<?php

declare(strict_types=1);

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

use App\Administration\Application\Command\ArchiveSubject\ArchiveSubjectCommand;
use App\Administration\Application\Command\ArchiveSubject\ArchiveSubjectHandler;
use App\Administration\Domain\Exception\SubjectNotFoundException;
use App\Administration\Domain\Model\SchoolClass\SchoolId;
use App\Administration\Domain\Model\Subject\Subject;
use App\Administration\Domain\Model\Subject\SubjectCode;
use App\Administration\Domain\Model\Subject\SubjectColor;
use App\Administration\Domain\Model\Subject\SubjectName;
use App\Administration\Domain\Model\Subject\SubjectStatus;
use App\Administration\Infrastructure\Persistence\InMemory\InMemorySubjectRepository;
use App\Shared\Domain\Clock;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class ArchiveSubjectHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string OTHER_TENANT_ID = '550e8400-e29b-41d4-a716-446655440099';
    private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440002';

    private InMemorySubjectRepository $subjectRepository;
    private Clock $clock;

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

    #[Test]
    public function itArchivesSubjectSuccessfully(): void
    {
        $subject = $this->createAndSaveSubject();
        $handler = new ArchiveSubjectHandler($this->subjectRepository, $this->clock);

        $command = new ArchiveSubjectCommand(
            subjectId: (string) $subject->id,
            tenantId: self::TENANT_ID,
        );

        $archivedSubject = $handler($command);

        self::assertSame(SubjectStatus::ARCHIVED, $archivedSubject->status);
        self::assertFalse($archivedSubject->estActive());
        self::assertNotNull($archivedSubject->deletedAt);
    }

    #[Test]
    public function itPersistsArchivedSubjectInRepository(): void
    {
        $subject = $this->createAndSaveSubject();
        $handler = new ArchiveSubjectHandler($this->subjectRepository, $this->clock);

        $command = new ArchiveSubjectCommand(
            subjectId: (string) $subject->id,
            tenantId: self::TENANT_ID,
        );

        $handler($command);

        // Retrieve from repository
        $retrievedSubject = $this->subjectRepository->get($subject->id);

        self::assertSame(SubjectStatus::ARCHIVED, $retrievedSubject->status);
    }

    #[Test]
    public function itThrowsExceptionWhenSubjectNotFound(): void
    {
        $handler = new ArchiveSubjectHandler($this->subjectRepository, $this->clock);

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

        $command = new ArchiveSubjectCommand(
            subjectId: '550e8400-e29b-41d4-a716-446655440099',
            tenantId: self::TENANT_ID,
        );
        $handler($command);
    }

    #[Test]
    public function itThrowsExceptionWhenSubjectBelongsToDifferentTenant(): void
    {
        $subject = $this->createAndSaveSubject();
        $handler = new ArchiveSubjectHandler($this->subjectRepository, $this->clock);

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

        // Try to archive with a different tenant ID
        $command = new ArchiveSubjectCommand(
            subjectId: (string) $subject->id,
            tenantId: self::OTHER_TENANT_ID,
        );
        $handler($command);
    }

    #[Test]
    public function itIsIdempotentWhenArchivingAlreadyArchivedSubject(): void
    {
        $subject = $this->createAndSaveSubject();
        $handler = new ArchiveSubjectHandler($this->subjectRepository, $this->clock);

        $command = new ArchiveSubjectCommand(
            subjectId: (string) $subject->id,
            tenantId: self::TENANT_ID,
        );

        // Archive twice
        $handler($command);
        $archivedAgain = $handler($command);

        self::assertSame(SubjectStatus::ARCHIVED, $archivedAgain->status);
    }

    private function createAndSaveSubject(): Subject
    {
        $subject = Subject::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            schoolId: SchoolId::fromString(self::SCHOOL_ID),
            name: new SubjectName('Mathématiques'),
            code: new SubjectCode('MATH'),
            color: new SubjectColor('#3B82F6'),
            createdAt: new DateTimeImmutable('2026-01-15 10:00:00'),
        );
        $this->subjectRepository->save($subject);

        return $subject;
    }
}
