<?php

declare(strict_types=1);

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

use App\Administration\Domain\Model\User\Role;
use App\Administration\Infrastructure\Security\CalendarVoter;
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 CalendarVoterTest extends TestCase
{
    private CalendarVoter $voter;

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

    #[Test]
    public function itAbstainsForUnrelatedAttributes(): void
    {
        $token = $this->tokenWithRole(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, [CalendarVoter::VIEW]);

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

    // --- VIEW ---

    #[Test]
    #[DataProvider('viewAllowedRolesProvider')]
    public function itGrantsViewToStaffRoles(string $role): void
    {
        $token = $this->tokenWithRole($role);

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

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

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

    #[Test]
    #[DataProvider('viewDeniedRolesProvider')]
    public function itDeniesViewToNonStaffRoles(string $role): void
    {
        $token = $this->tokenWithRole($role);

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

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

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

    // --- CONFIGURE ---

    #[Test]
    #[DataProvider('configureAllowedRolesProvider')]
    public function itGrantsConfigureToAdminRoles(string $role): void
    {
        $token = $this->tokenWithRole($role);

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

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

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

    #[Test]
    #[DataProvider('configureDeniedRolesProvider')]
    public function itDeniesConfigureToNonAdminRoles(string $role): void
    {
        $token = $this->tokenWithRole($role);

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

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

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

    private function tokenWithRole(string $role): TokenInterface
    {
        $user = $this->createMock(UserInterface::class);
        $user->method('getRoles')->willReturn([$role]);

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

        return $token;
    }
}
