<?php

declare(strict_types=1);

namespace NoahVet\Reef\Phpunit\Security\Voter;

use NoahVet\Reef\Phpunit\Fixture\FixtureRepository;
use NoahVet\Reef\Phpunit\Fixture\PermissionFixture;
use NoahVet\Reef\Phpunit\Fixture\ReefTokenFixture;
use NoahVet\Reef\Security\Authentication\Token\ReefOAuthToken;
use NoahVet\Reef\Security\IAM\Mapper\IAMResourceMapper;
use NoahVet\Reef\Security\IAM\Model\Resource;
use NoahVet\Reef\Security\IAM\Model\ResourceType;
use NoahVet\Reef\Security\Voter\IAMVoterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

class MockedIAMVoter implements IAMVoterInterface
{
    public function __construct(
        protected IAMResourceMapper $mapper,
        protected FixtureRepository $fixtureRepository,
    ) {
    }

    /**
     * @psalm-suppress ArgumentTypeCoercion
     */
    public function vote(TokenInterface $token, $subject, array $attributes): int
    {
        $bearer = null;

        if ($token instanceof ReefOAuthToken) {
            $bearer = $token->getBearerToken();
        }
        /* @psalm-suppress RedundantCondition */
        if (\is_a($token, 'HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken')) {
            $bearer = $token->getAccessToken();
        }

        $user = null;
        if (null === $bearer) {
            $user = $token->getUser();
        }

        if (null === $bearer && null === $user) {
            return self::ACCESS_ABSTAIN;
        } elseif (null !== $bearer) {
            $fixture = $this->fixtureRepository->findByOAuthToken($bearer);
        } else {
            $fixture = $this->fixtureRepository->findByUser($user);
        }

        // When asked for super admin only, check if the user is super-admin
        if (1 === \count($attributes) && 'reef:iam:principals:superAdmins' === $attributes[0]) {
            return $fixture?->isSuperAdmin() ? self::ACCESS_GRANTED : self::ACCESS_DENIED;
        }

        if ($subject instanceof ResourceType) {
            return $this->voteResourceType(
                $fixture,
                (string) $subject,
                $attributes,
            );
        }

        if ($subject instanceof Resource) {
            $subjectSlug = (string) $subject;
        } else {
            $subjectSlug = $this->mapper->map($subject)?->__toString();
        }

        return $this->voteResource(
            $fixture,
            $subjectSlug,
            $attributes,
        );
    }

    /**
     * @param array<mixed> $attributes
     *
     * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
     *
     * @psalm-return self::ACCESS_* must be transformed into @return on Symfony 7
     */
    protected function voteResource(
        ?ReefTokenFixture $fixture,
        ?string $subjectSlug,
        array $attributes,
    ): int {
        if (null === $subjectSlug) {
            return self::ACCESS_ABSTAIN;
        }
        if (!$fixture) {
            return self::ACCESS_DENIED;
        }

        $allowed = $fixture->getPermissions($subjectSlug)?->getAllowedPermission() ?? [];

        $allowedPermissions = \array_intersect($attributes, $allowed);

        return
            ($fixture->isSuperAdmin() || \count($allowedPermissions) === \count($attributes))
                ? self::ACCESS_GRANTED
                : self::ACCESS_DENIED;
    }

    /**
     * @param array<mixed> $attributes
     *
     * @return int either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED
     *
     * @psalm-return self::ACCESS_* must be transformed into @return on Symfony 7
     */
    protected function voteResourceType(
        ?ReefTokenFixture $fixture,
        ?string $subjectSlug,
        array $attributes,
    ): int {
        if (null === $fixture) {
            return self::ACCESS_ABSTAIN;
        }
        if (null === $subjectSlug) {
            return self::ACCESS_ABSTAIN;
        }

        $allowed = \array_reduce(
            $fixture->getPermissionsByResourceType($subjectSlug),
            static fn (bool $carry, PermissionFixture $permissionFixture): bool => $carry || \count(\array_intersect($permissionFixture->getAllowedPermission(), $attributes)) === \count($attributes),
            false,
        );

        return
            $fixture->isSuperAdmin() || $allowed
                ? self::ACCESS_GRANTED
                : self::ACCESS_DENIED;
    }
}
