<?php

declare(strict_types=1);

namespace App\Tests\Unit\Administration\Application\Query\GetSubjects;

use App\Administration\Application\Query\GetSubjects\GetSubjectsHandler;
use App\Administration\Application\Query\GetSubjects\GetSubjectsQuery;
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\Infrastructure\Persistence\InMemory\InMemorySubjectRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class GetSubjectsHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
    private const string SCHOOL_ID = '550e8400-e29b-41d4-a716-446655440020';

    private InMemorySubjectRepository $subjectRepository;
    private GetSubjectsHandler $handler;

    protected function setUp(): void
    {
        $this->subjectRepository = new InMemorySubjectRepository();
        $this->handler = new GetSubjectsHandler($this->subjectRepository);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
        );
        $result = ($this->handler)($query);

        self::assertCount(3, $result->items);
        self::assertSame(3, $result->total);
        self::assertSame(1, $result->page);
        self::assertSame(30, $result->limit);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            search: 'Mathématiques',
        );
        $result = ($this->handler)($query);

        self::assertCount(1, $result->items);
        self::assertSame('Mathématiques', $result->items[0]->name);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            search: 'FR',
        );
        $result = ($this->handler)($query);

        self::assertCount(1, $result->items);
        self::assertSame('FR', $result->items[0]->code);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            search: 'math',
        );
        $result = ($this->handler)($query);

        self::assertCount(1, $result->items);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            page: 1,
            limit: 2,
        );
        $result = ($this->handler)($query);

        self::assertCount(2, $result->items);
        self::assertSame(3, $result->total);
        self::assertSame(1, $result->page);
        self::assertSame(2, $result->limit);
        self::assertSame(2, $result->totalPages());
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            page: 2,
            limit: 2,
        );
        $result = ($this->handler)($query);

        self::assertCount(1, $result->items);
        self::assertSame(3, $result->total);
        self::assertSame(2, $result->page);
    }

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

        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            search: 'nonexistent',
        );
        $result = ($this->handler)($query);

        self::assertCount(0, $result->items);
        self::assertSame(0, $result->total);
    }

    #[Test]
    public function clampsInvalidPageToOne(): void
    {
        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            page: -1,
        );

        self::assertSame(1, $query->page);
    }

    #[Test]
    public function clampsExcessiveLimitToMax(): void
    {
        $query = new GetSubjectsQuery(
            tenantId: self::TENANT_ID,
            schoolId: self::SCHOOL_ID,
            limit: 999,
        );

        self::assertSame(100, $query->limit);
    }

    private function seedSubjects(): void
    {
        $tenantId = TenantId::fromString(self::TENANT_ID);
        $schoolId = SchoolId::fromString(self::SCHOOL_ID);
        $now = new DateTimeImmutable('2026-02-01 10:00:00');

        $this->subjectRepository->save(Subject::creer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: new SubjectName('Mathématiques'),
            code: new SubjectCode('MATH'),
            color: new SubjectColor('#3B82F6'),
            createdAt: $now,
        ));

        $this->subjectRepository->save(Subject::creer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: new SubjectName('Français'),
            code: new SubjectCode('FR'),
            color: new SubjectColor('#EF4444'),
            createdAt: $now,
        ));

        $this->subjectRepository->save(Subject::creer(
            tenantId: $tenantId,
            schoolId: $schoolId,
            name: new SubjectName('Histoire-Géo'),
            code: new SubjectCode('HG'),
            color: new SubjectColor('#10B981'),
            createdAt: $now,
        ));
    }
}
