<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Command\DesignateReplacement;

use App\Administration\Domain\Exception\TenantMismatchException;
use App\Administration\Domain\Exception\UserNotFoundException;
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\Scolarite\Application\Command\DesignateReplacement\DesignateReplacementCommand;
use App\Scolarite\Application\Command\DesignateReplacement\DesignateReplacementHandler;
use App\Scolarite\Domain\Exception\DatesRemplacementInvalidesException;
use App\Scolarite\Domain\Exception\RemplacementSameTeacherException;
use App\Scolarite\Domain\Exception\UtilisateurNonEnseignantException;
use App\Scolarite\Domain\Model\TeacherReplacement\ReplacementStatus;
use App\Scolarite\Domain\Model\TeacherReplacement\TeacherReplacementId;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryTeacherReplacementRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class DesignateReplacementHandlerTest 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 CREATED_BY_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 ADMIN_USER_ID = '550e8400-e29b-41d4-a716-446655440050';

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

    protected function setUp(): void
    {
        $this->replacementRepository = new InMemoryTeacherReplacementRepository();
        $this->userRepository = new InMemoryUserRepository();
        $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
    {
        $handler = $this->createHandler();
        $command = $this->createCommand();

        $replacement = $handler($command);

        self::assertNotEmpty((string) $replacement->id);
        self::assertSame(ReplacementStatus::ACTIVE, $replacement->status);
        self::assertNull($replacement->endedAt);
    }

    #[Test]
    public function itPersistsReplacementInRepository(): void
    {
        $handler = $this->createHandler();
        $command = $this->createCommand();

        $created = $handler($command);

        $replacement = $this->replacementRepository->get(
            TeacherReplacementId::fromString((string) $created->id),
            TenantId::fromString(self::TENANT_ID),
        );

        self::assertSame(ReplacementStatus::ACTIVE, $replacement->status);
        self::assertCount(1, $replacement->classes);
    }

    #[Test]
    public function itSetsAllPropertiesCorrectly(): void
    {
        $handler = $this->createHandler();
        $command = $this->createCommand();

        $replacement = $handler($command);

        self::assertTrue($replacement->replacedTeacherId->equals(UserId::fromString(self::REPLACED_TEACHER_ID)));
        self::assertTrue($replacement->replacementTeacherId->equals(UserId::fromString(self::REPLACEMENT_TEACHER_ID)));
        self::assertEquals(new DateTimeImmutable('2026-03-01'), $replacement->startDate);
        self::assertEquals(new DateTimeImmutable('2026-03-31'), $replacement->endDate);
        self::assertSame('Congé maladie', $replacement->reason);
    }

    #[Test]
    public function itThrowsWhenReplacedTeacherDoesNotExist(): void
    {
        $handler = $this->createHandler();

        $this->expectException(UserNotFoundException::class);
        $handler($this->createCommand(replacedTeacherId: '550e8400-e29b-41d4-a716-446655440088'));
    }

    #[Test]
    public function itThrowsWhenReplacementTeacherDoesNotExist(): void
    {
        $handler = $this->createHandler();

        $this->expectException(UserNotFoundException::class);
        $handler($this->createCommand(replacementTeacherId: '550e8400-e29b-41d4-a716-446655440088'));
    }

    #[Test]
    public function itThrowsWhenSameTeacher(): void
    {
        $handler = $this->createHandler();

        $this->expectException(RemplacementSameTeacherException::class);
        $handler($this->createCommand(replacementTeacherId: self::REPLACED_TEACHER_ID));
    }

    #[Test]
    public function itThrowsWhenEndDateBeforeStartDate(): void
    {
        $handler = $this->createHandler();

        $this->expectException(DatesRemplacementInvalidesException::class);
        $handler($this->createCommand(startDate: '2026-03-31', endDate: '2026-03-01'));
    }

    #[Test]
    public function itThrowsWhenReplacedTeacherBelongsToDifferentTenant(): void
    {
        $handler = $this->createHandler();

        $this->expectException(TenantMismatchException::class);
        $handler($this->createCommand(replacedTeacherId: self::OTHER_TENANT_TEACHER_ID));
    }

    #[Test]
    public function itThrowsWhenReplacementTeacherBelongsToDifferentTenant(): void
    {
        $handler = $this->createHandler();

        $this->expectException(TenantMismatchException::class);
        $handler($this->createCommand(replacementTeacherId: self::OTHER_TENANT_TEACHER_ID));
    }

    #[Test]
    public function itThrowsWhenReplacedTeacherIsNotATeacher(): void
    {
        $handler = $this->createHandler();

        $this->expectException(UtilisateurNonEnseignantException::class);
        $handler($this->createCommand(replacedTeacherId: self::ADMIN_USER_ID));
    }

    #[Test]
    public function itThrowsWhenReplacementTeacherIsNotATeacher(): void
    {
        $handler = $this->createHandler();

        $this->expectException(UtilisateurNonEnseignantException::class);
        $handler($this->createCommand(replacementTeacherId: self::ADMIN_USER_ID));
    }

    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 admin (pas enseignant) dans le même tenant
        $adminUser = User::reconstitute(
            id: UserId::fromString(self::ADMIN_USER_ID),
            email: new Email('admin@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($adminUser);
    }

    private function createHandler(): DesignateReplacementHandler
    {
        return new DesignateReplacementHandler(
            $this->replacementRepository,
            $this->userRepository,
            $this->clock,
        );
    }

    private function createCommand(
        ?string $replacedTeacherId = null,
        ?string $replacementTeacherId = null,
        ?string $startDate = null,
        ?string $endDate = null,
    ): DesignateReplacementCommand {
        return new DesignateReplacementCommand(
            tenantId: self::TENANT_ID,
            replacedTeacherId: $replacedTeacherId ?? self::REPLACED_TEACHER_ID,
            replacementTeacherId: $replacementTeacherId ?? self::REPLACEMENT_TEACHER_ID,
            startDate: $startDate ?? '2026-03-01',
            endDate: $endDate ?? '2026-03-31',
            classes: [
                ['classId' => self::CLASS_ID, 'subjectId' => self::SUBJECT_ID],
            ],
            reason: 'Congé maladie',
            createdBy: self::CREATED_BY_ID,
        );
    }
}
