<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Security;

use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Administration\Infrastructure\Security\StudentVoter;
use App\Shared\Domain\Tenant\TenantId;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\User\UserInterface;

final class StudentVoterTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';

    private StudentVoter $voter;

    protected function setUp(): void
    {
        $this->voter = new StudentVoter();
    }

    #[Test]
    public function itAbstainsForUnrelatedAttributes(): void
    {
        $token = $this->tokenWithSecurityUser(Role::ADMIN->value);

        $result = $this->voter->vote($token, null, ['SOME_OTHER_ATTRIBUTE']);

        self::assertSame(Voter::ACCESS_ABSTAIN, $result);
    }

    #[Test]
    public function itDeniesAccessToUnauthenticatedUsers(): void
    {
        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn(null);

        $result = $this->voter->vote($token, null, [StudentVoter::VIEW]);

        self::assertSame(Voter::ACCESS_DENIED, $result);
    }

    #[Test]
    public function itDeniesAccessToNonSecurityUserInstances(): void
    {
        $user = $this->createMock(UserInterface::class);
        $user->method('getRoles')->willReturn([Role::ADMIN->value]);

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($user);

        $result = $this->voter->vote($token, null, [StudentVoter::VIEW]);

        self::assertSame(Voter::ACCESS_DENIED, $result);
    }

    // --- VIEW ---

    #[Test]
    #[DataProvider('allowedRolesProvider')]
    public function itGrantsViewToAllowedRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::VIEW]);

        self::assertSame(Voter::ACCESS_GRANTED, $result);
    }

    #[Test]
    #[DataProvider('deniedRolesProvider')]
    public function itDeniesViewToOtherRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::VIEW]);

        self::assertSame(Voter::ACCESS_DENIED, $result);
    }

    // --- CREATE ---

    #[Test]
    #[DataProvider('allowedRolesProvider')]
    public function itGrantsCreateToAllowedRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::CREATE]);

        self::assertSame(Voter::ACCESS_GRANTED, $result);
    }

    #[Test]
    #[DataProvider('deniedRolesProvider')]
    public function itDeniesCreateToOtherRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::CREATE]);

        self::assertSame(Voter::ACCESS_DENIED, $result);
    }

    // --- MANAGE ---

    #[Test]
    #[DataProvider('allowedRolesProvider')]
    public function itGrantsManageToAllowedRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::MANAGE]);

        self::assertSame(Voter::ACCESS_GRANTED, $result);
    }

    #[Test]
    #[DataProvider('deniedRolesProvider')]
    public function itDeniesManageToOtherRoles(string $role): void
    {
        $token = $this->tokenWithSecurityUser($role);

        $result = $this->voter->vote($token, null, [StudentVoter::MANAGE]);

        self::assertSame(Voter::ACCESS_DENIED, $result);
    }

    // --- Data Providers ---

    /**
     * @return iterable<string, array{string}>
     */
    public static function allowedRolesProvider(): iterable
    {
        yield 'SUPER_ADMIN' => [Role::SUPER_ADMIN->value];
        yield 'ADMIN' => [Role::ADMIN->value];
        yield 'SECRETARIAT' => [Role::SECRETARIAT->value];
    }

    /**
     * @return iterable<string, array{string}>
     */
    public static function deniedRolesProvider(): iterable
    {
        yield 'PROF' => [Role::PROF->value];
        yield 'VIE_SCOLAIRE' => [Role::VIE_SCOLAIRE->value];
        yield 'PARENT' => [Role::PARENT->value];
        yield 'ELEVE' => [Role::ELEVE->value];
    }

    private function tokenWithSecurityUser(string $role): TokenInterface
    {
        $securityUser = new SecurityUser(
            UserId::fromString('550e8400-e29b-41d4-a716-446655440010'),
            'test@example.com',
            'hashed_password',
            TenantId::fromString(self::TENANT_ID),
            [$role],
        );

        $token = $this->createMock(TokenInterface::class);
        $token->method('getUser')->willReturn($securityUser);

        return $token;
    }
}
