<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Infrastructure\Api\Processor;

use ApiPlatform\Metadata\Post;
use App\Administration\Application\Port\ActiveRoleStore;
use App\Administration\Application\Service\RoleContext;
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\Administration\Infrastructure\Api\Processor\SwitchRoleProcessor;
use App\Administration\Infrastructure\Api\Resource\SwitchRoleInput;
use App\Administration\Infrastructure\Api\Resource\SwitchRoleOutput;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryUserRepository;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

/**
 * Tests for SwitchRoleProcessor.
 *
 * Key invariants:
 * - Only authenticated users can switch roles
 * - User must have the target role assigned
 * - Suspended accounts cannot switch roles
 * - Invalid role strings are rejected
 */
final class SwitchRoleProcessorTest extends TestCase
{
    private const string USER_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string TENANT_ID = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890';

    private Security $security;
    private InMemoryUserRepository $userRepository;
    private ActiveRoleStore $activeRoleStore;

    protected function setUp(): void
    {
        $this->security = $this->createMock(Security::class);
        $this->userRepository = new InMemoryUserRepository();
        $this->activeRoleStore = $this->createMock(ActiveRoleStore::class);
    }

    #[Test]
    public function switchesRoleSuccessfully(): void
    {
        $user = $this->createMultiRoleUser();
        $this->userRepository->save($user);
        $this->authenticateAs($user);

        $this->activeRoleStore->expects(self::once())
            ->method('store')
            ->with($user, Role::ADMIN);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = Role::ADMIN->value;

        $output = $processor->process($input, new Post());

        self::assertInstanceOf(SwitchRoleOutput::class, $output);
        self::assertSame(Role::ADMIN->value, $output->activeRole);
        self::assertSame('Directeur', $output->activeRoleLabel);
    }

    #[Test]
    public function throwsUnauthorizedWhenNotAuthenticated(): void
    {
        $this->security->method('getUser')->willReturn(null);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = Role::PROF->value;

        $this->expectException(UnauthorizedHttpException::class);
        $this->expectExceptionMessage('Authentification requise');

        $processor->process($input, new Post());
    }

    #[Test]
    public function throwsBadRequestWhenRoleIsInvalid(): void
    {
        $user = $this->createMultiRoleUser();
        $this->userRepository->save($user);
        $this->authenticateAs($user);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = 'ROLE_NONEXISTENT';

        $this->expectException(BadRequestHttpException::class);
        $this->expectExceptionMessage('invalide');

        $processor->process($input, new Post());
    }

    #[Test]
    public function throwsBadRequestWhenRoleIsNull(): void
    {
        $user = $this->createMultiRoleUser();
        $this->userRepository->save($user);
        $this->authenticateAs($user);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = null;

        $this->expectException(BadRequestHttpException::class);
        $this->expectExceptionMessage('invalide');

        $processor->process($input, new Post());
    }

    #[Test]
    public function throwsNotFoundWhenUserDoesNotExist(): void
    {
        $securityUser = new SecurityUser(
            userId: UserId::fromString(self::USER_ID),
            email: 'user@example.com',
            hashedPassword: '$argon2id$hashed',
            tenantId: TenantId::fromString(self::TENANT_ID),
            roles: [Role::PROF->value, Role::ADMIN->value],
        );
        $this->security->method('getUser')->willReturn($securityUser);

        // User NOT saved to repository

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = Role::ADMIN->value;

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

        $processor->process($input, new Post());
    }

    #[Test]
    public function throwsBadRequestWhenRoleNotAssigned(): void
    {
        // User only has PROF role
        $user = User::reconstitute(
            id: UserId::fromString(self::USER_ID),
            email: new Email('user@example.com'),
            roles: [Role::PROF],
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'Ecole Test',
            statut: StatutCompte::ACTIF,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15T10:00:00+00:00'),
            hashedPassword: '$argon2id$hashed',
            activatedAt: new DateTimeImmutable('2026-01-16T10:00:00+00:00'),
            consentementParental: null,
            firstName: 'Jean',
            lastName: 'Dupont',
        );
        $this->userRepository->save($user);
        $this->authenticateAs($user);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = Role::ADMIN->value;

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

        $processor->process($input, new Post());
    }

    #[Test]
    public function throwsAccessDeniedWhenAccountSuspended(): void
    {
        $user = User::reconstitute(
            id: UserId::fromString(self::USER_ID),
            email: new Email('user@example.com'),
            roles: [Role::PROF, Role::ADMIN],
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'Ecole Test',
            statut: StatutCompte::SUSPENDU,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15T10:00:00+00:00'),
            hashedPassword: '$argon2id$hashed',
            activatedAt: new DateTimeImmutable('2026-01-16T10:00:00+00:00'),
            consentementParental: null,
            firstName: 'Jean',
            lastName: 'Dupont',
            blockedAt: new DateTimeImmutable('2026-02-10T10:00:00+00:00'),
            blockedReason: 'Reason',
        );
        $this->userRepository->save($user);
        $this->authenticateAs($user);

        $processor = $this->createProcessor();

        $input = new SwitchRoleInput();
        $input->role = Role::ADMIN->value;

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

        $processor->process($input, new Post());
    }

    private function createProcessor(): SwitchRoleProcessor
    {
        $roleContext = new RoleContext($this->activeRoleStore);

        return new SwitchRoleProcessor(
            $this->security,
            $this->userRepository,
            $roleContext,
        );
    }

    private function createMultiRoleUser(): User
    {
        return User::reconstitute(
            id: UserId::fromString(self::USER_ID),
            email: new Email('user@example.com'),
            roles: [Role::PROF, Role::ADMIN],
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolName: 'Ecole Test',
            statut: StatutCompte::ACTIF,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15T10:00:00+00:00'),
            hashedPassword: '$argon2id$hashed',
            activatedAt: new DateTimeImmutable('2026-01-16T10:00:00+00:00'),
            consentementParental: null,
            firstName: 'Jean',
            lastName: 'Dupont',
        );
    }

    private function authenticateAs(User $user): void
    {
        $securityUser = new SecurityUser(
            userId: $user->id,
            email: (string) $user->email,
            hashedPassword: $user->hashedPassword ?? '',
            tenantId: $user->tenantId,
            roles: array_values(array_map(
                static fn (Role $r) => $r->value,
                $user->roles,
            )),
        );
        $this->security->method('getUser')->willReturn($securityUser);
    }
}
