<?php

declare(strict_types=1);

namespace App\Tests\Unit\Scolarite\Application\Query\GetChildrenHomework;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Administration\Domain\Model\Subject\SubjectId;
use App\Administration\Domain\Model\User\UserId;
use App\Scolarite\Application\Port\ParentChildrenReader;
use App\Scolarite\Application\Port\ScheduleDisplayReader;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Application\Query\GetChildrenHomework\GetChildrenHomeworkHandler;
use App\Scolarite\Application\Query\GetChildrenHomework\GetChildrenHomeworkQuery;
use App\Scolarite\Domain\Model\Homework\Homework;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkAttachmentRepository;
use App\Scolarite\Infrastructure\Persistence\InMemory\InMemoryHomeworkRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class GetChildrenHomeworkHandlerTest extends TestCase
{
    private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440001';
    private const string PARENT_ID = '550e8400-e29b-41d4-a716-446655440060';
    private const string CHILD_A_ID = '550e8400-e29b-41d4-a716-446655440050';
    private const string CHILD_B_ID = '550e8400-e29b-41d4-a716-446655440051';
    private const string CLASS_A_ID = '550e8400-e29b-41d4-a716-446655440020';
    private const string CLASS_B_ID = '550e8400-e29b-41d4-a716-446655440021';
    private const string SUBJECT_MATH_ID = '550e8400-e29b-41d4-a716-446655440030';
    private const string SUBJECT_FRENCH_ID = '550e8400-e29b-41d4-a716-446655440031';
    private const string TEACHER_ID = '550e8400-e29b-41d4-a716-446655440010';

    private InMemoryHomeworkRepository $homeworkRepository;
    private InMemoryHomeworkAttachmentRepository $attachmentRepository;

    protected function setUp(): void
    {
        $this->homeworkRepository = new InMemoryHomeworkRepository();
        $this->attachmentRepository = new InMemoryHomeworkAttachmentRepository();
    }

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

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
        ));

        self::assertSame([], $result);
    }

    #[Test]
    public function itReturnsHomeworkForSingleChild(): void
    {
        $this->givenHomework(title: 'Exercices chapitre 5', dueDate: '2026-04-15', classId: self::CLASS_A_ID);

        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
            ],
            classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
        ));

        self::assertCount(1, $result);
        self::assertSame(self::CHILD_A_ID, $result[0]->childId);
        self::assertSame('Emma', $result[0]->firstName);
        self::assertSame('Dupont', $result[0]->lastName);
        self::assertCount(1, $result[0]->homework);
        self::assertSame('Exercices chapitre 5', $result[0]->homework[0]->title);
    }

    #[Test]
    public function itReturnsHomeworkForMultipleChildren(): void
    {
        $this->givenHomework(title: 'Maths 6A', dueDate: '2026-04-15', classId: self::CLASS_A_ID);
        $this->givenHomework(title: 'Maths 6B', dueDate: '2026-04-16', classId: self::CLASS_B_ID);

        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
                ['studentId' => self::CHILD_B_ID, 'firstName' => 'Lucas', 'lastName' => 'Dupont'],
            ],
            classMap: [
                self::CHILD_A_ID => self::CLASS_A_ID,
                self::CHILD_B_ID => self::CLASS_B_ID,
            ],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
        ));

        self::assertCount(2, $result);
        self::assertSame('Emma', $result[0]->firstName);
        self::assertCount(1, $result[0]->homework);
        self::assertSame('Maths 6A', $result[0]->homework[0]->title);
        self::assertSame('Lucas', $result[1]->firstName);
        self::assertCount(1, $result[1]->homework);
        self::assertSame('Maths 6B', $result[1]->homework[0]->title);
    }

    #[Test]
    public function itFiltersToSpecificChildWhenChildIdProvided(): void
    {
        $this->givenHomework(title: 'Maths 6A', dueDate: '2026-04-15', classId: self::CLASS_A_ID);
        $this->givenHomework(title: 'Maths 6B', dueDate: '2026-04-16', classId: self::CLASS_B_ID);

        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
                ['studentId' => self::CHILD_B_ID, 'firstName' => 'Lucas', 'lastName' => 'Dupont'],
            ],
            classMap: [
                self::CHILD_A_ID => self::CLASS_A_ID,
                self::CHILD_B_ID => self::CLASS_B_ID,
            ],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
            childId: self::CHILD_B_ID,
        ));

        self::assertCount(1, $result);
        self::assertSame('Lucas', $result[0]->firstName);
        self::assertCount(1, $result[0]->homework);
        self::assertSame('Maths 6B', $result[0]->homework[0]->title);
    }

    #[Test]
    public function itReturnsEmptyHomeworkWhenChildHasNoClass(): void
    {
        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
            ],
            classMap: [],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
        ));

        self::assertCount(1, $result);
        self::assertSame('Emma', $result[0]->firstName);
        self::assertSame([], $result[0]->homework);
    }

    #[Test]
    public function itReturnsEmptyWhenChildIdDoesNotMatchAnyChild(): void
    {
        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
            ],
            classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
            childId: '550e8400-e29b-41d4-a716-446655449999',
        ));

        self::assertSame([], $result);
    }

    #[Test]
    public function itFiltersBySubjectWhenProvided(): void
    {
        $this->givenHomework(title: 'Maths', dueDate: '2026-04-15', classId: self::CLASS_A_ID, subjectId: self::SUBJECT_MATH_ID);
        $this->givenHomework(title: 'Français', dueDate: '2026-04-16', classId: self::CLASS_A_ID, subjectId: self::SUBJECT_FRENCH_ID);

        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
            ],
            classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
            subjectId: self::SUBJECT_MATH_ID,
        ));

        self::assertCount(1, $result);
        self::assertCount(1, $result[0]->homework);
        self::assertSame('Maths', $result[0]->homework[0]->title);
    }

    #[Test]
    public function itSortsHomeworkByDueDateAscending(): void
    {
        $this->givenHomework(title: 'Lointain', dueDate: '2026-05-20', classId: self::CLASS_A_ID);
        $this->givenHomework(title: 'Proche', dueDate: '2026-04-10', classId: self::CLASS_A_ID);
        $this->givenHomework(title: 'Milieu', dueDate: '2026-04-25', classId: self::CLASS_A_ID);

        $handler = $this->createHandler(
            children: [
                ['studentId' => self::CHILD_A_ID, 'firstName' => 'Emma', 'lastName' => 'Dupont'],
            ],
            classMap: [self::CHILD_A_ID => self::CLASS_A_ID],
        );

        $result = $handler(new GetChildrenHomeworkQuery(
            parentId: self::PARENT_ID,
            tenantId: self::TENANT_ID,
        ));

        self::assertCount(1, $result);
        $titles = array_map(static fn ($hw) => $hw->title, $result[0]->homework);
        self::assertSame(['Proche', 'Milieu', 'Lointain'], $titles);
    }

    /**
     * @param array<array{studentId: string, firstName: string, lastName: string}> $children
     * @param array<string, string>                                                $classMap studentId => classId
     */
    private function createHandler(
        array $children = [],
        array $classMap = [],
    ): GetChildrenHomeworkHandler {
        $parentChildrenReader = new class($children) implements ParentChildrenReader {
            /** @param array<array{studentId: string, firstName: string, lastName: string}> $children */
            public function __construct(private readonly array $children)
            {
            }

            public function childrenOf(string $guardianId, TenantId $tenantId): array
            {
                return $this->children;
            }
        };

        $studentClassReader = new class($classMap) implements StudentClassReader {
            /** @param array<string, string> $classMap */
            public function __construct(private readonly array $classMap)
            {
            }

            public function currentClassId(string $studentId, TenantId $tenantId): ?string
            {
                return $this->classMap[$studentId] ?? null;
            }
        };

        $displayReader = new class implements ScheduleDisplayReader {
            public function subjectDisplay(string $tenantId, string ...$subjectIds): array
            {
                $map = [];

                foreach ($subjectIds as $id) {
                    $map[$id] = ['name' => 'Mathématiques', 'color' => '#3b82f6'];
                }

                return $map;
            }

            public function teacherNames(string $tenantId, string ...$teacherIds): array
            {
                $map = [];

                foreach ($teacherIds as $id) {
                    $map[$id] = 'Jean Dupont';
                }

                return $map;
            }
        };

        return new GetChildrenHomeworkHandler(
            $parentChildrenReader,
            $studentClassReader,
            $this->homeworkRepository,
            $this->attachmentRepository,
            $displayReader,
        );
    }

    private function givenHomework(
        string $title,
        string $dueDate,
        string $classId = self::CLASS_A_ID,
        string $subjectId = self::SUBJECT_MATH_ID,
    ): Homework {
        $homework = Homework::creer(
            tenantId: TenantId::fromString(self::TENANT_ID),
            classId: ClassId::fromString($classId),
            subjectId: SubjectId::fromString($subjectId),
            teacherId: UserId::fromString(self::TEACHER_ID),
            title: $title,
            description: null,
            dueDate: new DateTimeImmutable($dueDate),
            now: new DateTimeImmutable('2026-03-12 10:00:00'),
        );

        $this->homeworkRepository->save($homework);

        return $homework;
    }
}
