<?php

declare(strict_types=1);

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

use ApiPlatform\Metadata\Post;
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\Persistence\InMemory\InMemoryUserRepository;
use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Application\Command\DesignateReplacement\DesignateReplacementHandler;
use App\Scolarite\Infrastructure\Api\Processor\CreateTeacherReplacementProcessor;
use App\Scolarite\Infrastructure\Api\Resource\TeacherReplacementResource;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use App\Shared\Infrastructure\Tenant\TenantConfig;
use App\Shared\Infrastructure\Tenant\TenantContext;
use App\Shared\Infrastructure\Tenant\TenantId as InfraTenantId;
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;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Core\Authorization\AccessDecision;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

final class CreateTeacherReplacementProcessorTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string REPLACED_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';
    private const string REPLACEMENT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440011';
    private const string CLASS_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string SUBJECT_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string ADMIN_USER_ID = '550e8400-e29b-41d4-a716-446655440099';
    private const string OTHER_TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string OTHER_TENANT_TEACHER_ID = '550e8400-e29b-41d4-a716-446655440012';
    private const string NON_TEACHER_USER_ID = '550e8400-e29b-41d4-a716-446655440050';

    private InMemoryTeacherReplacementRepository $replacementRepository;
    private InMemoryUserRepository $userRepository;
    private TenantContext $tenantContext;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->replacementRepository = new InMemoryTeacherReplacementRepository();
        $this->userRepository = new InMemoryUserRepository();
        $this->tenantContext = new TenantContext();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-15 10:00:00');
            }
        };

        $this->seedTestData();
    }

    #[Test]
    public function itCreatesReplacementSuccessfully(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();

        $result = $processor->process($data, new Post());

        self::assertInstanceOf(TeacherReplacementResource::class, $result);
        self::assertNotNull($result->id);
        self::assertSame(self::REPLACED_TEACHER_ID, $result->replacedTeacherId);
        self::assertSame(self::REPLACEMENT_TEACHER_ID, $result->replacementTeacherId);
        self::assertSame('2026-03-01', $result->startDate);
        self::assertSame('2026-03-31', $result->endDate);
        self::assertSame('active', $result->status);
        self::assertSame('Congé maladie', $result->reason);
        self::assertCount(1, $result->classes);
    }

    #[Test]
    public function itRejectsUnauthorizedAccess(): void
    {
        $processor = $this->createProcessor(granted: false);
        $this->setTenant();

        $this->expectException(AccessDeniedHttpException::class);
        $processor->process($this->createResource(), new Post());
    }

    #[Test]
    public function itRejectsRequestWithoutTenant(): void
    {
        $processor = $this->createProcessor(granted: true);

        $this->expectException(UnauthorizedHttpException::class);
        $processor->process($this->createResource(), new Post());
    }

    #[Test]
    public function itMapsSameTeacherExceptionToBadRequest(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->replacementTeacherId = self::REPLACED_TEACHER_ID;

        $this->expectException(BadRequestHttpException::class);
        $processor->process($data, new Post());
    }

    #[Test]
    public function itMapsInvalidDatesExceptionToBadRequest(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->startDate = '2026-03-31';
        $data->endDate = '2026-03-01';

        $this->expectException(BadRequestHttpException::class);
        $processor->process($data, new Post());
    }

    #[Test]
    public function itMapsUserNotFoundExceptionToNotFound(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->replacedTeacherId = '550e8400-e29b-41d4-a716-446655440088';

        $this->expectException(NotFoundHttpException::class);
        $processor->process($data, new Post());
    }

    #[Test]
    public function itMapsInvalidUuidToBadRequest(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->replacedTeacherId = 'not-a-valid-uuid';

        $this->expectException(BadRequestHttpException::class);
        $processor->process($data, new Post());
    }

    #[Test]
    public function itMapsTenantMismatchExceptionToBadRequest(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->replacedTeacherId = self::OTHER_TENANT_TEACHER_ID;

        $this->expectException(BadRequestHttpException::class);
        $processor->process($data, new Post());
    }

    #[Test]
    public function itMapsNonTeacherExceptionToBadRequest(): void
    {
        $processor = $this->createProcessor(granted: true);
        $this->setTenant();

        $data = $this->createResource();
        $data->replacedTeacherId = self::NON_TEACHER_USER_ID;

        $this->expectException(BadRequestHttpException::class);
        $processor->process($data, new Post());
    }

    private function seedTestData(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);

        $replacedTeacher = User::reconstitute(
            id: UserId::fromString(self::REPLACED_TEACHER_ID),
            email: new Email('replaced@example.com'),
            roles: [Role::PROF],
            tenantId: $tenantId,
            schoolName: 'École Test',
            statut: StatutCompte::EN_ATTENTE,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15'),
            hashedPassword: null,
            activatedAt: null,
            consentementParental: null,
        );
        $this->userRepository->save($replacedTeacher);

        $replacementTeacher = User::reconstitute(
            id: UserId::fromString(self::REPLACEMENT_TEACHER_ID),
            email: new Email('replacement@example.com'),
            roles: [Role::PROF],
            tenantId: $tenantId,
            schoolName: 'École Test',
            statut: StatutCompte::EN_ATTENTE,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15'),
            hashedPassword: null,
            activatedAt: null,
            consentementParental: null,
        );
        $this->userRepository->save($replacementTeacher);

        // Enseignant d'un autre tenant
        $otherTenantTeacher = User::reconstitute(
            id: UserId::fromString(self::OTHER_TENANT_TEACHER_ID),
            email: new Email('other-tenant@example.com'),
            roles: [Role::PROF],
            tenantId: TenantId::fromString(self::OTHER_TENANT_ID),
            schoolName: 'Autre École',
            statut: StatutCompte::EN_ATTENTE,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15'),
            hashedPassword: null,
            activatedAt: null,
            consentementParental: null,
        );
        $this->userRepository->save($otherTenantTeacher);

        // Utilisateur non-enseignant dans le même tenant
        $nonTeacherUser = User::reconstitute(
            id: UserId::fromString(self::NON_TEACHER_USER_ID),
            email: new Email('admin-user@example.com'),
            roles: [Role::ADMIN],
            tenantId: $tenantId,
            schoolName: 'École Test',
            statut: StatutCompte::EN_ATTENTE,
            dateNaissance: null,
            createdAt: new DateTimeImmutable('2026-01-15'),
            hashedPassword: null,
            activatedAt: null,
            consentementParental: null,
        );
        $this->userRepository->save($nonTeacherUser);
    }

    private function setTenant(): void
    {
        $this->tenantContext->setCurrentTenant(new TenantConfig(
            tenantId: InfraTenantId::fromString(self::TENANT_ID),
            subdomain: 'test',
            databaseUrl: 'sqlite:///:memory:',
        ));
    }

    private function createResource(): TeacherReplacementResource
    {
        $data = new TeacherReplacementResource();
        $data->replacedTeacherId = self::REPLACED_TEACHER_ID;
        $data->replacementTeacherId = self::REPLACEMENT_TEACHER_ID;
        $data->startDate = '2026-03-01';
        $data->endDate = '2026-03-31';
        $data->classes = [
            ['classId' => self::CLASS_ID, 'subjectId' => self::SUBJECT_ID],
        ];
        $data->reason = 'Congé maladie';

        return $data;
    }

    private function createProcessor(bool $granted): CreateTeacherReplacementProcessor
    {
        $authChecker = new class($granted) implements AuthorizationCheckerInterface {
            public function __construct(private readonly bool $granted)
            {
            }

            public function isGranted(mixed $attribute, mixed $subject = null, ?AccessDecision $accessDecision = null): bool
            {
                return $this->granted;
            }
        };

        $eventBus = new class implements MessageBusInterface {
            public function dispatch(object $message, array $stamps = []): Envelope
            {
                return new Envelope($message);
            }
        };

        $handler = new DesignateReplacementHandler(
            $this->replacementRepository,
            $this->userRepository,
            $this->clock,
        );

        $securityUser = new SecurityUser(
            UserId::fromString(self::ADMIN_USER_ID),
            'admin@example.com',
            'hashed',
            TenantId::fromString(self::TENANT_ID),
            [Role::ADMIN->value],
        );

        $security = $this->createStub(Security::class);
        $security->method('getUser')->willReturn($securityUser);

        return new CreateTeacherReplacementProcessor(
            $handler,
            $this->tenantContext,
            $eventBus,
            $authChecker,
            $security,
        );
    }
}
