<?php

declare(strict_types=1);

namespace App\Scolarite\Infrastructure\Api\Controller;

use App\Administration\Infrastructure\Security\SecurityUser;
use App\Scolarite\Application\Query\GetStudentSchedule\GetStudentScheduleHandler;
use App\Scolarite\Application\Query\GetStudentSchedule\GetStudentScheduleQuery;
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 function date;

use InvalidArgumentException;
use RuntimeException;
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\ServiceUnavailableHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

use function usort;

/**
 * Endpoints de consultation de l'emploi du temps pour l'élève connecté.
 */
#[IsGranted(ScheduleSlotVoter::VIEW)]
final readonly class StudentScheduleController
{
    public function __construct(
        private Security $security,
        private GetStudentScheduleHandler $handler,
    ) {
    }

    /**
     * EDT du jour pour l'élève connecté.
     */
    #[Route('/api/me/schedule/day/{date}', name: 'api_student_schedule_day', methods: ['GET'])]
    public function day(string $date): JsonResponse
    {
        $slots = $this->resolveSchedule($date);

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

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

    /**
     * EDT de la semaine pour l'élève connecté.
     */
    #[Route('/api/me/schedule/week/{date}', name: 'api_student_schedule_week', methods: ['GET'])]
    public function week(string $date): JsonResponse
    {
        $slots = $this->resolveSchedule($date);

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

    /**
     * Prochain cours pour l'élève connecté.
     */
    #[Route('/api/me/schedule/next-class', name: 'api_student_schedule_next_class', methods: ['GET'])]
    public function nextClass(): JsonResponse
    {
        $today = date('Y-m-d');
        $now = date('H:i');

        $slots = $this->resolveSchedule($today);

        // Chercher le prochain cours : d'abord aujourd'hui après l'heure actuelle
        $nextSlot = $this->findNextSlot($slots, $today, $now);

        if ($nextSlot === null) {
            return new JsonResponse(['data' => null]);
        }

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

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

        try {
            return ($this->handler)(new GetStudentScheduleQuery(
                studentId: $user->userId(),
                tenantId: $user->tenantId(),
                date: $date,
            ));
        } catch (InvalidArgumentException $e) {
            throw new BadRequestHttpException($e->getMessage(), $e);
        } catch (RuntimeException $e) {
            throw new ServiceUnavailableHttpException(null, "L'emploi du temps n'est pas encore disponible.", $e);
        }
    }

    /**
     * @param array<StudentScheduleSlotDto> $slots
     */
    private function findNextSlot(array $slots, string $today, string $now): ?StudentScheduleSlotDto
    {
        $candidates = [];

        foreach ($slots as $slot) {
            // Cours aujourd'hui qui n'a pas encore commencé
            if ($slot->date === $today && $slot->startTime > $now) {
                $candidates[] = $slot;

                continue;
            }

            // Cours des jours suivants de la semaine
            if ($slot->date > $today) {
                $candidates[] = $slot;
            }
        }

        if ($candidates === []) {
            return null;
        }

        usort($candidates, static function (StudentScheduleSlotDto $a, StudentScheduleSlotDto $b): int {
            $cmp = $a->date <=> $b->date;

            return $cmp !== 0 ? $cmp : $a->startTime <=> $b->startTime;
        });

        return $candidates[0];
    }

    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,
        ];
    }
}
