<?php

declare(strict_types=1);

namespace App\Libraries;

use DateTimeImmutable;
use DateTimeZone;

class BookingSlotGenerator
{
    public function generateSlotsForDate(
        array $schedule,
        array $shifts,
        array $exceptions,
        DateTimeImmutable $date,
        array $bookedStarts = []
    ): array {
        $timezone = !empty($schedule['timezone']) ? (string)$schedule['timezone'] : 'America/Los_Angeles';
        $tz = new DateTimeZone($timezone);

        $days = !empty($schedule['days_json']) ? (array)json_decode((string)$schedule['days_json'], true) : [];
        if (!is_array($days)) {
            $days = [];
        }
        $dayIndexToAbbrev = [0 => 'Sun', 1 => 'Mon', 2 => 'Tue', 3 => 'Wed', 4 => 'Thu', 5 => 'Fri', 6 => 'Sat'];
        $normalizedDays = [];
        foreach ($days as $d) {
            if (is_int($d) && isset($dayIndexToAbbrev[$d])) {
                $normalizedDays[] = $dayIndexToAbbrev[$d];
            } elseif (is_string($d)) {
                $normalizedDays[] = $d;
            }
        }
        $normalizedDays = array_values(array_unique($normalizedDays));

        $dateInTz = $date->setTimezone($tz);
        $dayAbbrev = $dateInTz->format('D');

        if (!in_array($dayAbbrev, $normalizedDays, true)) {
            return [];
        }

        $dateStr = $dateInTz->format('Y-m-d');
        $exception = null;
        foreach ($exceptions as $ex) {
            if (($ex['exception_date'] ?? null) === $dateStr) {
                $exception = $ex;
                break;
            }
        }

        if ($exception !== null && !empty($exception['is_closed'])) {
            return [];
        }

        $effectiveShifts = [];
        if ($exception !== null && !empty($exception['custom_open']) && !empty($exception['custom_close'])) {
            $effectiveShifts[] = [
                'shift_start' => $exception['custom_open'],
                'shift_end'   => $exception['custom_close'],
            ];
        } else {
            $effectiveShifts = $shifts;
        }

        if (empty($effectiveShifts)) {
            return [];
        }

        $meetingMinutes = (int)($schedule['meeting_duration_minutes'] ?? 30);
        $breakMinutes   = (int)($schedule['break_duration_minutes'] ?? 0);
        $meetingSeconds = $meetingMinutes * 60;
        $breakSeconds   = $breakMinutes * 60;

        $bookedRanges = [];
        foreach ($bookedStarts as $bs) {
            $startTs = null;
            $endTs   = null;

            if ($bs instanceof DateTimeImmutable) {
                $dt = $bs->setTimezone($tz);
                $startTs = $dt->getTimestamp();
            } elseif (is_array($bs)) {
                $sRaw = $bs['start'] ?? $bs['starts_at'] ?? $bs['start_str'] ?? null;
                $eRaw = $bs['end']   ?? $bs['ends_at']   ?? $bs['end_str']   ?? null;
                if ($sRaw instanceof DateTimeImmutable) {
                    $sDt = $sRaw->setTimezone($tz);
                    $startTs = $sDt->getTimestamp();
                } elseif (is_string($sRaw) && $sRaw !== '') {
                    try {
                        $sDt = new DateTimeImmutable($sRaw, $tz);
                        $startTs = $sDt->getTimestamp();
                    } catch (\Throwable $e) {
                        $startTs = null;
                    }
                }
                if ($eRaw instanceof DateTimeImmutable) {
                    $eDt = $eRaw->setTimezone($tz);
                    $endTs = $eDt->getTimestamp();
                } elseif (is_string($eRaw) && $eRaw !== '') {
                    try {
                        $eDt = new DateTimeImmutable($eRaw, $tz);
                        $endTs = $eDt->getTimestamp();
                    } catch (\Throwable $e) {
                        $endTs = null;
                    }
                }
            } elseif (is_string($bs) && $bs !== '') {
                try {
                    $dt = new DateTimeImmutable($bs, $tz);
                    $startTs = $dt->getTimestamp();
                } catch (\Throwable $e) {
                    $startTs = null;
                }
            }

            if ($startTs === null) {
                continue;
            }
            if ($endTs === null || $endTs <= $startTs) {
                $endTs = $startTs + $meetingSeconds;
            }
            $bookedRanges[] = [$startTs, $endTs];
        }

        $slots = [];

        foreach ($effectiveShifts as $shift) {
            $shiftStartStr = (string)($shift['shift_start'] ?? '');
            $shiftEndStr   = (string)($shift['shift_end'] ?? '');
            if ($shiftStartStr === '' || $shiftEndStr === '') {
                continue;
            }

            [$sh, $sm, $ss] = array_pad(explode(':', $shiftStartStr), 3, 0);
            [$eh, $em, $es] = array_pad(explode(':', $shiftEndStr), 3, 0);

            $shiftStartDt = $dateInTz->setTime((int)$sh, (int)$sm, (int)$ss);
            $shiftEndDt   = $dateInTz->setTime((int)$eh, (int)$em, (int)$es);

            $cursor = $shiftStartDt->getTimestamp();
            $shiftEndTs = $shiftEndDt->getTimestamp();

            while (true) {
                $slotEndTs = $cursor + $meetingSeconds;
                if ($slotEndTs > $shiftEndTs) {
                    break;
                }

                $startDt = (new DateTimeImmutable())->setTimestamp($cursor)->setTimezone($tz);
                $endDt   = (new DateTimeImmutable())->setTimestamp($slotEndTs)->setTimezone($tz);

                $available = true;
                foreach ($bookedRanges as [$btStart, $btEnd]) {
                    if (!($slotEndTs <= $btStart || $cursor >= $btEnd)) {
                        $available = false;
                        break;
                    }
                }

                $slots[] = [
                    'start_str' => $startDt->format('Y-m-d H:i:s'),
                    'end_str'   => $endDt->format('Y-m-d H:i:s'),
                    'start_ut'  => $cursor,
                    'end_ut'    => $slotEndTs,
                    'available' => $available,
                ];

                $cursor = $slotEndTs;
                if ($cursor + $meetingSeconds <= $shiftEndTs) {
                    $cursor += $breakSeconds;
                }
            }
        }

        usort($slots, static function ($a, $b) {
            return $a['start_ut'] <=> $b['start_ut'];
        });

        return $slots;
    }
}
