<?php

namespace App\Services;

use App\Enums\ChampionshipMatchStatusEnum;
use App\Models\AcademySchedule;
use App\Models\ChampionshipMatch;
use Carbon\Carbon;

class CourtAvailabilityService
{
    private const SLOT_STEP_MINUTES = 30;
    private const DAY_START_HOUR = 6;
    private const DAY_END_HOUR = 23;

    /**
     * Check if a court is blocked by any active academy schedule for the given date and time.
     * Single query, no loops.
     */
    public function isCourtBlockedByAcademy(int $courtId, string $date, string $startTime, string $endTime): bool
    {
        $dayOfWeek = (int) Carbon::parse($date)->dayOfWeek; // 0=Sunday, 6=Saturday

        return AcademySchedule::blockingSlot($courtId, $date, $dayOfWeek, $startTime, $endTime)->exists()
            || $this->isCourtBlockedByChampionshipMatch($courtId, $date, $startTime, $endTime);
    }

    /**
     * Get court IDs that are blocked by any academy schedule for the given slot.
     * Single query, no loops.
     */
    public function getCourtIdsBlockedByAcademyForSlot(string $date, string $startTime, string $endTime): array
    {
        $dayOfWeek = (int) Carbon::parse($date)->dayOfWeek;

        $academyCourtIds = AcademySchedule::active()
            ->forDay($dayOfWeek)
            ->overlappingDateRange($date)
            ->overlappingTimeRange($startTime, $endTime)
            ->pluck('court_id')
            ->unique()
            ->values()
            ->all();

        $championshipCourtIds = $this->getCourtIdsBlockedByChampionshipMatchForSlot($date, $startTime, $endTime);

        return array_values(array_unique(array_merge($academyCourtIds, $championshipCourtIds)));
    }

    /**
     * Check if the given time slot is blocked by another academy schedule (for create/update validation).
     */
    public function isSlotBlockedByOtherAcademySchedule(
        int $courtId,
        int $dayOfWeek,
        string $startDate,
        string $endDate,
        string $startTime,
        string $endTime,
        ?int $excludeAcademyScheduleId = null
    ): bool {
        $ranges = $this->getBlockedTimeRangesForCourtDay(
            $courtId,
            $dayOfWeek,
            $startDate,
            $endDate,
            $excludeAcademyScheduleId
        );
        $slotStart = $this->timeToMinutes($startTime);
        $slotEnd = $this->timeToMinutes($endTime);
        foreach ($ranges as $range) {
            if ($slotStart < $range['end'] && $slotEnd > $range['start']) {
                return true;
            }
        }
        return false;
    }

    /**
     * Get available time slots for an academy schedule (create/edit).
     * Slot duration comes from the academy's game (duration in minutes). Slots that overlap with another
     * academy schedule on same court/day/date range are marked disabled.
     */
    public function getAvailableSlotsForAcademySchedule(
        int $courtId,
        int $dayOfWeek,
        string $startDate,
        string $endDate,
        int $slotDurationMinutes = 60,
        ?int $excludeAcademyScheduleId = null
    ): array {
        $slotDurationMinutes = max(15, min(120, $slotDurationMinutes)); // clamp 15–120 for sanity
        $slots = $this->generateTimeSlotsForDay($slotDurationMinutes);

        $blockedRanges = $this->getBlockedTimeRangesForCourtDay(
            $courtId,
            $dayOfWeek,
            $startDate,
            $endDate,
            $excludeAcademyScheduleId
        );

        foreach ($slots as $idx => $slot) {
            $slots[$idx]['disabled'] = $this->slotOverlapsAnyRange($slot, $blockedRanges);
        }

        return $slots;
    }

    /**
     * Generate slots from 06:00 to 23:00 with given step (minutes).
     */
    private function generateTimeSlotsForDay(int $stepMinutes): array
    {
        $slots = [];
        $start = Carbon::today()->setTime(self::DAY_START_HOUR, 0, 0);
        $endOfDay = Carbon::today()->setTime(self::DAY_END_HOUR, 0, 0);

        while ($start->lt($endOfDay)) {
            $end = $start->copy()->addMinutes($stepMinutes);
            $slots[] = [
                'start_time' => $start->format('H:i'),
                'end_time'   => $end->format('H:i'),
                'disabled'   => false,
            ];
            $start->addMinutes($stepMinutes);
        }

        return $slots;
    }

    /**
     * Single query: get all (start_time, end_time) from academy_schedules that block this court/day/date range.
     */
    private function getBlockedTimeRangesForCourtDay(
        int $courtId,
        int $dayOfWeek,
        string $startDate,
        string $endDate,
        ?int $excludeId
    ): array {
        $query = AcademySchedule::active()
            ->forCourt($courtId)
            ->forDay($dayOfWeek)
            ->where(function ($q) use ($startDate, $endDate) {
                $q->whereDate('start_date', '<=', $endDate)
                    ->whereDate('end_date', '>=', $startDate);
            })
            ->select('start_time', 'end_time');

        if ($excludeId !== null) {
            $query->where('id', '!=', $excludeId);
        }

        return $query->get()->map(function ($row) {
            return [
                'start' => $this->timeToMinutes($row->start_time),
                'end'   => $this->timeToMinutes($row->end_time),
            ];
        })->all();
    }

    private function isCourtBlockedByChampionshipMatch(int $courtId, string $date, string $startTime, string $endTime): bool
    {
        return ChampionshipMatch::query()
            ->where('court_id', $courtId)
            ->whereDate('match_date', $date)
            ->whereIn('status', [
                ChampionshipMatchStatusEnum::SCHEDULED->value,
                ChampionshipMatchStatusEnum::PLAYING->value,
            ])
            ->where(function ($q) use ($startTime, $endTime) {
                $q->where('start_time', '<', $endTime)
                    ->where('end_time', '>', $startTime);
            })
            ->exists();
    }

    private function getCourtIdsBlockedByChampionshipMatchForSlot(string $date, string $startTime, string $endTime): array
    {
        return ChampionshipMatch::query()
            ->whereNotNull('court_id')
            ->whereDate('match_date', $date)
            ->whereIn('status', [
                ChampionshipMatchStatusEnum::SCHEDULED->value,
                ChampionshipMatchStatusEnum::PLAYING->value,
            ])
            ->where(function ($q) use ($startTime, $endTime) {
                $q->where('start_time', '<', $endTime)
                    ->where('end_time', '>', $startTime);
            })
            ->pluck('court_id')
            ->map(fn ($id) => (int) $id)
            ->unique()
            ->values()
            ->all();
    }

    private function timeToMinutes(string $time): int
    {
        $parts = explode(':', $time);
        return ((int) ($parts[0] ?? 0)) * 60 + (int) ($parts[1] ?? 0);
    }

    private function slotOverlapsAnyRange(array $slot, array $ranges): bool
    {
        $slotStart = $this->timeToMinutes($slot['start_time']);
        $slotEnd = $this->timeToMinutes($slot['end_time']);

        foreach ($ranges as $range) {
            if ($slotStart < $range['end'] && $slotEnd > $range['start']) {
                return true;
            }
        }

        return false;
    }
}
