<?php

declare(strict_types=1);

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

use App\Administration\Application\Command\ArchiveClass\ArchiveClassCommand;
use App\Administration\Application\Command\ArchiveClass\ArchiveClassHandler;
use App\Administration\Application\Query\HasStudentsInClass\HasStudentsInClassQuery;
use App\Administration\Domain\Exception\ClasseNonSupprimableException;
use App\Administration\Domain\Model\SchoolClass\AcademicYearId;
use App\Administration\Domain\Model\SchoolClass\ClassName;
use App\Administration\Domain\Model\SchoolClass\ClassStatus;
use App\Administration\Domain\Model\SchoolClass\SchoolClass;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\SchoolClass\SchoolLevel;
use App\Administration\Infrastructure\Persistence\InMemory\InMemoryClassRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\HandledStamp;

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

    private InMemoryClassRepository $classRepository;
    private Clock $clock;

    protected function setUp(): void
    {
        $this->classRepository = new InMemoryClassRepository();
        $this->clock = new class implements Clock {
            public function now(): DateTimeImmutable
            {
                return new DateTimeImmutable('2026-02-01 10:00:00');
            }
        };
    }

    #[Test]
    public function itArchivesEmptyClass(): void
    {
        $class = $this->createAndSaveClass();
        $queryBus = $this->createQueryBusReturning(0);
        $handler = new ArchiveClassHandler($this->classRepository, $queryBus, $this->clock);

        $command = new ArchiveClassCommand(classId: (string) $class->id);

        $handler($command);

        $archivedClass = $this->classRepository->get($class->id);
        self::assertSame(ClassStatus::ARCHIVED, $archivedClass->status);
        self::assertNotNull($archivedClass->deletedAt);
    }

    #[Test]
    public function itThrowsExceptionWhenStudentsAreAffected(): void
    {
        $class = $this->createAndSaveClass();
        $queryBus = $this->createQueryBusReturning(5);
        $handler = new ArchiveClassHandler($this->classRepository, $queryBus, $this->clock);

        $command = new ArchiveClassCommand(classId: (string) $class->id);

        $this->expectException(ClasseNonSupprimableException::class);
        $this->expectExceptionMessage('5 élève(s)');

        $handler($command);
    }

    #[Test]
    public function itDoesNotModifyStatusWhenAlreadyArchived(): void
    {
        $class = $this->createAndSaveClass();
        $archiveTime = new DateTimeImmutable('2026-01-20 10:00:00');
        $class->archiver($archiveTime);
        $this->classRepository->save($class);

        $queryBus = $this->createQueryBusReturning(0);
        $handler = new ArchiveClassHandler($this->classRepository, $queryBus, $this->clock);

        $command = new ArchiveClassCommand(classId: (string) $class->id);

        $handler($command);

        $archivedClass = $this->classRepository->get($class->id);
        // deletedAt should not have changed
        self::assertEquals($archiveTime, $archivedClass->deletedAt);
    }

    private function createAndSaveClass(): SchoolClass
    {
        $class = SchoolClass::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            academicYearId: AcademicYearId::fromString(self::ACADEMIC_YEAR_ID),
            name: new ClassName('6ème A'),
            level: SchoolLevel::SIXIEME,
            capacity: 30,
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );

        $this->classRepository->save($class);

        return $class;
    }

    private function createQueryBusReturning(int $studentCount): MessageBusInterface
    {
        return new class($studentCount) implements MessageBusInterface {
            public function __construct(private readonly int $studentCount)
            {
            }

            public function dispatch(object $message, array $stamps = []): Envelope
            {
                if (!$message instanceof HasStudentsInClassQuery) {
                    throw new RuntimeException('Unexpected message type');
                }

                $envelope = new Envelope($message);

                return $envelope->with(new HandledStamp($this->studentCount, 'handler'));
            }
        };
    }
}
