<?php

namespace NoahVet\Reef\Infrastructure\Security\Api;

use NoahVet\Reef\Domain\Security\Model\Resource;
use NoahVet\Reef\Domain\Security\Model\ResourceType;
use NoahVet\Reef\Domain\Security\Port\PermissionCheckerInterface;
use NoahVet\Reef\Factory\ClientFactory;
use NoahVet\Reef\Iam\Client;
use NoahVet\Reef\Iam\Model\PermissionGrant;
use NoahVet\Reef\Iam\Model\PermissionGrantList;
use NoahVet\Reef\Iam\Model\UserInfo;
use NoahVet\Reef\Security\Authentication\BearerClientHMacComputer;

class JanePermissionChecker implements PermissionCheckerInterface
{
    /**
     * @var array<string, Client>
     */
    private array $clients = [];

    public function __construct(
        private readonly BearerClientHMacComputer $hmacComputer,
        private readonly ClientFactory $clientFactory,
    ) {
    }

    #[\Override]
    public function check(Resource|ResourceType $subject, array $permissions, string $bearerToken): bool
    {
        $client = $this->getClient($bearerToken);

        if ($subject instanceof ResourceType) {
            return $this->checkResourceType($client, $bearerToken, (string) $subject, $permissions);
        }

        return $this->checkResource($client, $bearerToken, (string) $subject, $permissions);
    }

    #[\Override]
    public function checkGroup(string $groupSlug, string $bearerToken): bool
    {
        /** @var UserInfo|null $userinfo */
        $userinfo = $this->getClient($bearerToken)->oauthUserinfo();

        return \in_array($groupSlug, $userinfo?->getGroups() ?? [], true);
    }

    /**
     * @param string[] $permissions
     */
    private function checkResource(Client $client, string $bearerToken, string $subjectSlug, array $permissions): bool
    {
        /** @var PermissionGrant $grant */
        $grant = $client->getResourceItemDetailedPermissions(
            $subjectSlug,
            $this->hmacComputer->computeClientHMacHeader($bearerToken),
        );

        return \count(\array_intersect($permissions, $grant->getAllowed())) === \count($permissions);
    }

    /**
     * @param string[] $permissions
     */
    private function checkResourceType(Client $client, string $bearerToken, string $subjectSlug, array $permissions): bool
    {
        /** @var PermissionGrantList $grantList */
        $grantList = $client->getResourceTypeItemDetailedPermissions(
            $subjectSlug,
            [],
            $this->hmacComputer->computeClientHMacHeader($bearerToken),
        );

        /** @var array<string, PermissionGrant>|\ArrayObject<string, PermissionGrant> $resourcesGrants */
        $resourcesGrants = $grantList->getResources();

        if ($resourcesGrants instanceof \ArrayObject) {
            $resourcesGrants = $resourcesGrants->getArrayCopy();
        }

        $resourceAllowed = \array_reduce(
            \array_values($resourcesGrants),
            static fn (bool $carry, PermissionGrant $grant): bool => $carry || \count(\array_intersect($grant->getAllowed(), $permissions)) === \count($permissions),
            false,
        );

        /** @var array<string, PermissionGrant>|\ArrayObject<string, PermissionGrant> $groupsGrants */
        $groupsGrants = $grantList->getGroups();
        if (!\is_array($groupsGrants)) {
            $groupsGrants = $groupsGrants->getArrayCopy();
        }

        $groupAllowed = \array_reduce(
            \array_values($groupsGrants),
            static fn (bool $carry, PermissionGrant $grant): bool => $carry || \count(\array_intersect($grant->getAllowed(), $permissions)) === \count($permissions),
            false,
        );

        return $resourceAllowed || $groupAllowed;
    }

    private function getClient(string $bearerToken): Client
    {
        if (!isset($this->clients[$bearerToken])) {
            $this->clients[$bearerToken] = $this->clientFactory->createClient($bearerToken, Client::class);
        }

        return $this->clients[$bearerToken];
    }
}
