<?php

declare(strict_types=1);

namespace App\Scolarite\Application\Query\GetChildrenSchedule;

use App\Administration\Domain\Model\SchoolClass\ClassId;
use App\Scolarite\Application\Port\CurrentCalendarProvider;
use App\Scolarite\Application\Port\ParentChildrenReader;
use App\Scolarite\Application\Port\ScheduleDisplayReader;
use App\Scolarite\Application\Port\StudentClassReader;
use App\Scolarite\Application\Query\GetStudentSchedule\StudentScheduleSlotDto;
use App\Scolarite\Application\Service\ScheduleResolver;
use App\Scolarite\Domain\Model\Schedule\ResolvedScheduleSlot;
use App\Shared\Domain\Tenant\TenantId;

use function array_filter;
use function array_map;
use function array_unique;
use function array_values;

use DateTimeImmutable;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;

#[AsMessageHandler(bus: 'query.bus')]
final readonly class GetChildrenScheduleHandler
{
    public function __construct(
        private ParentChildrenReader $parentChildrenReader,
        private StudentClassReader $studentClassReader,
        private ScheduleResolver $scheduleResolver,
        private CurrentCalendarProvider $calendarProvider,
        private ScheduleDisplayReader $displayReader,
    ) {
    }

    /** @return array<ChildScheduleDto> */
    public function __invoke(GetChildrenScheduleQuery $query): array
    {
        $tenantId = TenantId::fromString($query->tenantId);
        $allChildren = $this->parentChildrenReader->childrenOf($query->parentId, $tenantId);

        if ($allChildren === []) {
            return [];
        }

        // When a specific child is requested, only resolve that child's schedule
        $children = $query->childId !== null
            ? array_values(array_filter($allChildren, static fn (array $c): bool => $c['studentId'] === $query->childId))
            : $allChildren;

        if ($children === []) {
            return [];
        }

        $date = new DateTimeImmutable($query->date);
        $weekStart = $this->mondayOfWeek($date);
        $calendar = $this->calendarProvider->forCurrentYear($tenantId);

        $result = [];

        foreach ($children as $child) {
            $classId = $this->studentClassReader->currentClassId($child['studentId'], $tenantId);

            if ($classId === null) {
                $result[] = new ChildScheduleDto(
                    childId: $child['studentId'],
                    firstName: $child['firstName'],
                    lastName: $child['lastName'],
                    slots: [],
                );

                continue;
            }

            $resolved = $this->scheduleResolver->resolveForWeek(
                ClassId::fromString($classId),
                $weekStart,
                $tenantId,
                $calendar,
            );

            $slots = $this->enrichSlots($resolved, $query->tenantId);

            $result[] = new ChildScheduleDto(
                childId: $child['studentId'],
                firstName: $child['firstName'],
                lastName: $child['lastName'],
                slots: $slots,
            );
        }

        return $result;
    }

    /**
     * @param array<ResolvedScheduleSlot> $slots
     *
     * @return array<StudentScheduleSlotDto>
     */
    private function enrichSlots(array $slots, string $tenantId): array
    {
        if ($slots === []) {
            return [];
        }

        $subjectIds = array_values(array_unique(
            array_map(static fn (ResolvedScheduleSlot $s): string => (string) $s->subjectId, $slots),
        ));
        $teacherIds = array_values(array_unique(
            array_map(static fn (ResolvedScheduleSlot $s): string => (string) $s->teacherId, $slots),
        ));

        $subjects = $this->displayReader->subjectDisplay($tenantId, ...$subjectIds);
        $teacherNames = $this->displayReader->teacherNames($tenantId, ...$teacherIds);

        return array_map(
            static fn (ResolvedScheduleSlot $s): StudentScheduleSlotDto => StudentScheduleSlotDto::fromResolved(
                $s,
                $subjects[(string) $s->subjectId]['name'] ?? '',
                $subjects[(string) $s->subjectId]['color'] ?? null,
                $teacherNames[(string) $s->teacherId] ?? '',
            ),
            $slots,
        );
    }

    private function mondayOfWeek(DateTimeImmutable $date): DateTimeImmutable
    {
        $dayOfWeek = (int) $date->format('N');

        if ($dayOfWeek === 1) {
            return $date;
        }

        return $date->modify('-' . ($dayOfWeek - 1) . ' days');
    }
}
