<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Command\UpdatePeriod;

use App\Administration\Application\Command\UpdatePeriod\UpdatePeriodCommand;
use App\Administration\Application\Command\UpdatePeriod\UpdatePeriodHandler;
use App\Administration\Application\Port\GradeExistenceChecker;
use App\Administration\Domain\Exception\PeriodeAvecNotesException;
use App\Administration\Domain\Exception\PeriodeNonTrouveeException;
use App\Administration\Domain\Exception\PeriodesNonConfigureesException;
use App\Administration\Domain\Exception\PeriodsOverlapException;
use App\Administration\Domain\Model\AcademicYear\DefaultPeriods;
use App\Administration\Domain\Model\AcademicYear\PeriodType;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryPeriodConfigurationRepository;
use App\Administration\Infrastructure\Service\NoOpGradeExistenceChecker;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Override;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;

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

    private InMemoryPeriodConfigurationRepository $repository;
    private UpdatePeriodHandler $handler;
    private Clock $clock;
    private MessageBusInterface $eventBus;

    protected function setUp(): void
    {
        $this->repository = new InMemoryPeriodConfigurationRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2025-10-15 10:00:00');
            }
        };
        $this->eventBus = new class implements MessageBusInterface {
            public function dispatch(object $message, array $stamps = []): Envelope
            {
                return new Envelope($message);
            }
        };
        $this->handler = new UpdatePeriodHandler($this->repository, new NoOpGradeExistenceChecker(), $this->clock, $this->eventBus);
    }

    #[Test]
    public function itRejectsOverlappingPeriodDates(): void
    {
        $this->seedTrimesterConfig();

        $command = new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 1,
            startDate: '2025-09-01',
            endDate: '2025-12-15',
        );

        $this->expectException(PeriodsOverlapException::class);
        ($this->handler)($command);
    }

    #[Test]
    public function itUpdatesValidPeriodDates(): void
    {
        $this->seedTrimesterConfig();

        $command = new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 1,
            startDate: '2025-09-02',
            endDate: '2025-11-30',
        );

        $config = ($this->handler)($command);

        self::assertSame('2025-09-02', $config->periods[0]->startDate->format('Y-m-d'));
        self::assertSame('2025-11-30', $config->periods[0]->endDate->format('Y-m-d'));
    }

    #[Test]
    public function itRejectsWhenNoConfigurationExists(): void
    {
        $this->expectException(PeriodesNonConfigureesException::class);

        ($this->handler)(new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 1,
            startDate: '2025-09-01',
            endDate: '2025-11-30',
        ));
    }

    #[Test]
    public function itRejectsUnknownSequence(): void
    {
        $this->seedTrimesterConfig();

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

        ($this->handler)(new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 99,
            startDate: '2025-09-01',
            endDate: '2025-11-30',
        ));
    }

    #[Test]
    public function itRejectsUpdateWhenPeriodHasGradesWithoutConfirmation(): void
    {
        $this->seedTrimesterConfig();

        $gradeChecker = $this->createGradeCheckerWithGrades();

        $handler = new UpdatePeriodHandler($this->repository, $gradeChecker, $this->clock, $this->eventBus);

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

        $handler(new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 1,
            startDate: '2025-09-02',
            endDate: '2025-11-30',
        ));
    }

    #[Test]
    public function itAllowsUpdateWhenPeriodHasGradesWithConfirmation(): void
    {
        $this->seedTrimesterConfig();

        $gradeChecker = $this->createGradeCheckerWithGrades();

        $handler = new UpdatePeriodHandler($this->repository, $gradeChecker, $this->clock, $this->eventBus);

        $config = $handler(new UpdatePeriodCommand(
            tenantId: self::TENANT_ID,
            academicYearId: self::ACADEMIC_YEAR_ID,
            sequence: 1,
            startDate: '2025-09-02',
            endDate: '2025-11-30',
            confirmImpact: true,
        ));

        self::assertSame('2025-09-02', $config->periods[0]->startDate->format('Y-m-d'));
    }

    private function createGradeCheckerWithGrades(): GradeExistenceChecker
    {
        return new class implements GradeExistenceChecker {
            #[Override]
            public function hasGradesInPeriod(TenantId $tenantId, AcademicYearId $academicYearId, int $periodSequence): bool
            {
                return true;
            }

            #[Override]
            public function hasGradesForYear(TenantId $tenantId, SchoolId $schoolId, AcademicYearId $academicYearId): bool
            {
                return true;
            }
        };
    }

    private function seedTrimesterConfig(): void
    {
        $config = DefaultPeriods::forType(PeriodType::TRIMESTER, 2025);
        $this->repository->save(
            TenantId::fromString(self::TENANT_ID),
            AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            $config,
        );
    }
}
