<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Api\Controller;

use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Application\Query\GetChildrenSchedule\ChildScheduleDto;
use App\Scolarite\Application\Query\GetChildrenSchedule\GetChildrenScheduleHandler;
use App\Scolarite\Application\Query\GetChildrenSchedule\GetChildrenScheduleQuery;
use App\Scolarite\Application\Query\GetStudentSchedule\StudentScheduleSlotDto;
use App\Scolarite\Infrastructure\Security\ScheduleSlotVoter;

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

use DateTimeImmutable;
use InvalidArgumentException;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

use function usort;

/**
 * Endpoints de consultation de l'emploi du temps des enfants pour le parent connecté.
 */
#[IsGranted(ScheduleSlotVoter::VIEW)]
final readonly class ParentScheduleController
{
    public function __construct(
        private Security $security,
        private GetChildrenScheduleHandler $handler,
    ) {
    }

    /**
     * EDT du jour d'un enfant.
     */
    #[Route('/api/me/children/{childId}/schedule/day/{date}', name: 'api_parent_child_schedule_day', methods: ['GET'])]
    public function childDay(string $childId, string $date): JsonResponse
    {
        $childSchedule = $this->findChildSchedule($childId, $date);

        $daySlots = array_values(array_filter(
            $childSchedule->slots,
            static fn (StudentScheduleSlotDto $s): bool => $s->date === $date,
        ));

        return new JsonResponse(['data' => $this->serializeSlots($daySlots)]);
    }

    /**
     * EDT de la semaine d'un enfant.
     */
    #[Route('/api/me/children/{childId}/schedule/week/{date}', name: 'api_parent_child_schedule_week', methods: ['GET'])]
    public function childWeek(string $childId, string $date): JsonResponse
    {
        $childSchedule = $this->findChildSchedule($childId, $date);

        return new JsonResponse(['data' => $this->serializeSlots($childSchedule->slots)]);
    }

    /**
     * Résumé EDT du jour pour tous les enfants.
     */
    #[Route('/api/me/children/schedule/summary', name: 'api_parent_children_schedule_summary', methods: ['GET'])]
    public function summary(): JsonResponse
    {
        $now = new DateTimeImmutable();
        $today = $now->format('Y-m-d');
        $currentTime = $now->format('H:i');
        $children = $this->resolveAllChildren($today);

        $summaries = array_map(function (ChildScheduleDto $child) use ($today, $currentTime): array {
            $todaySlots = array_values(array_filter(
                $child->slots,
                static fn (StudentScheduleSlotDto $s): bool => $s->date === $today,
            ));

            usort($todaySlots, static fn (StudentScheduleSlotDto $a, StudentScheduleSlotDto $b): int => $a->startTime <=> $b->startTime);

            return [
                'childId' => $child->childId,
                'firstName' => $child->firstName,
                'lastName' => $child->lastName,
                'todaySlots' => $this->serializeSlots($todaySlots),
                'nextClass' => $this->findNextSlot($todaySlots, $currentTime),
            ];
        }, $children);

        return new JsonResponse(['data' => $summaries]);
    }

    private function findChildSchedule(string $childId, string $date): ChildScheduleDto
    {
        $user = $this->getSecurityUser();

        try {
            $children = ($this->handler)(new GetChildrenScheduleQuery(
                parentId: $user->userId(),
                tenantId: $user->tenantId(),
                date: $date,
                childId: $childId,
            ));
        } catch (InvalidArgumentException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        }

        if ($children === []) {
            throw new NotFoundHttpException('Enfant non trouvé ou non lié à ce parent.');
        }

        return $children[0];
    }

    /**
     * @return array<ChildScheduleDto>
     */
    private function resolveAllChildren(string $date): array
    {
        $user = $this->getSecurityUser();

        try {
            return ($this->handler)(new GetChildrenScheduleQuery(
                parentId: $user->userId(),
                tenantId: $user->tenantId(),
                date: $date,
            ));
        } catch (InvalidArgumentException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        }
    }

    /**
     * @param array<StudentScheduleSlotDto> $todaySlots
     *
     * @return array<string, mixed>|null
     */
    private function findNextSlot(array $todaySlots, string $currentTime): ?array
    {
        foreach ($todaySlots as $slot) {
            if ($slot->startTime > $currentTime) {
                return $this->serializeSlot($slot);
            }
        }

        return null;
    }

    private function getSecurityUser(): SecurityUser
    {
        $user = $this->security->getUser();

        if (!$user instanceof SecurityUser) {
            throw new AccessDeniedHttpException('Authentification requise.');
        }

        return $user;
    }

    /**
     * @param array<StudentScheduleSlotDto> $slots
     *
     * @return array<array<string, mixed>>
     */
    private function serializeSlots(array $slots): array
    {
        return array_map($this->serializeSlot(...), $slots);
    }

    /**
     * @return array<string, mixed>
     */
    private function serializeSlot(StudentScheduleSlotDto $slot): array
    {
        return [
            'slotId' => $slot->slotId,
            'date' => $slot->date,
            'dayOfWeek' => $slot->dayOfWeek,
            'startTime' => $slot->startTime,
            'endTime' => $slot->endTime,
            'subjectId' => $slot->subjectId,
            'subjectName' => $slot->subjectName,
            'subjectColor' => $slot->subjectColor,
            'teacherId' => $slot->teacherId,
            'teacherName' => $slot->teacherName,
            'room' => $slot->room,
            'isModified' => $slot->isModified,
            'exceptionId' => $slot->exceptionId,
        ];
    }
}
