<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Service;

use App\Administration\Application\Port\ActiveRoleStore;
use App\Administration\Application\Service\RoleContext;
use App\Administration\Domain\Exception\RoleNonAttribueException;
use App\Administration\Domain\Model\User\Email;
use App\Administration\Domain\Model\User\Role;
use App\Administration\Domain\Model\User\StatutCompte;
use App\Administration\Domain\Model\User\User;
use App\Administration\Domain\Model\User\UserId;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use DomainException;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

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

    private InMemoryActiveRoleStore $store;
    private RoleContext $roleContext;

    protected function setUp(): void
    {
        $this->store = new InMemoryActiveRoleStore();
        $this->roleContext = new RoleContext($this->store);
    }

    #[Test]
    public function itReturnsPrimaryRoleWhenNoActiveRoleStored(): void
    {
        $user = $this->createUser(Role::PROF);

        self::assertSame(Role::PROF, $this->roleContext->getActiveRole($user));
    }

    #[Test]
    public function itSwitchesToAnotherRole(): void
    {
        $user = $this->createActiveUser(Role::PROF);
        $user->attribuerRole(Role::ADMIN, new DateTimeImmutable());

        $this->roleContext->switchTo($user, Role::ADMIN);

        self::assertSame(Role::ADMIN, $this->roleContext->getActiveRole($user));
    }

    #[Test]
    public function itThrowsWhenSwitchingToUnassignedRole(): void
    {
        $user = $this->createActiveUser(Role::PROF);

        $this->expectException(RoleNonAttribueException::class);

        $this->roleContext->switchTo($user, Role::ADMIN);
    }

    #[Test]
    public function itClearsActiveRole(): void
    {
        $user = $this->createActiveUser(Role::PROF);
        $user->attribuerRole(Role::ADMIN, new DateTimeImmutable());

        $this->roleContext->switchTo($user, Role::ADMIN);
        $this->roleContext->clear($user);

        self::assertSame(Role::PROF, $this->roleContext->getActiveRole($user));
    }

    #[Test]
    public function itThrowsWhenAccountIsNotActive(): void
    {
        $user = $this->createActiveUser(Role::PROF);
        $user->attribuerRole(Role::ADMIN, new DateTimeImmutable());
        $user->bloquer('Test', new DateTimeImmutable());

        $this->expectException(DomainException::class);

        $this->roleContext->switchTo($user, Role::ADMIN);
    }

    private function createUser(Role $role): User
    {
        return User::creer(
            email: new Email('user@example.com'),
            role: $role,
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'École Test',
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
    }

    private function createActiveUser(Role $role): User
    {
        return User::reconstitute(
            id: UserId::generate(),
            email: new Email('active@example.com'),
            roles: [$role],
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'École Test',
            statut: StatutCompte::ACTIF,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
            hashedPassword: 'hashed',
            activatedAt: new DateTimeImmutable('2026-01-16 10:00:00'),
            consentementParental: null,
        );
    }
}

/**
 * @internal
 */
final class InMemoryActiveRoleStore implements ActiveRoleStore
{
    /** @var array<string, Role> */
    private array $roles = [];

    public function store(User $user, Role $role): void
    {
        $this->roles[(string) $user->id] = $role;
    }

    public function get(User $user): ?Role
    {
        return $this->roles[(string) $user->id] ?? null;
    }

    public function clear(User $user): void
    {
        unset($this->roles[(string) $user->id]);
    }
}
