<?php

namespace App\Services;

use App\Models\AvailabilityException;
use App\Models\AvailabilityRule;
use App\Models\Service;
use App\Services\Calendar\GoogleCalendarService;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Collection;

class AvailabilityService
{
    public function __construct(private ?GoogleCalendarService $google = null)
    {
    }

    /**
     * Compute available slots for a service between two dates.
     */
    public function getSlots(Service $service, Carbon $from, Carbon $to): Collection
    {
        $rules = $service->availabilityRules()->get();
        $exceptions = $service->availabilityExceptions()
            ->whereBetween('date', [$from->toDateString(), $to->toDateString()])
            ->get()
            ->keyBy(fn ($exception) => $exception->date->toDateString());

        $slots = collect();
        $period = CarbonPeriod::create($from, $to);

        foreach ($period as $date) {
            $dayRules = $rules->where('day_of_week', $date->dayOfWeek);
            $exception = $exceptions->get($date->toDateString());

            if ($exception && $exception->is_closed) {
                continue;
            }

            if ($exception && $exception->slots) {
                foreach ($exception->slots as $slot) {
                    $slots->push([
                        'start' => Carbon::parse("{$date->toDateString()} {$slot['start']}"),
                        'end' => Carbon::parse("{$date->toDateString()} {$slot['end']}"),
                        'timezone' => $exception->timezone ?? $service->meta['timezone'] ?? 'UTC',
                    ]);
                }
                continue;
            }

            foreach ($dayRules as $rule) {
                $start = Carbon::parse($rule->start_time, $rule->timezone)->setDateFrom($date);
                $end = Carbon::parse($rule->end_time, $rule->timezone)->setDateFrom($date);

                $slotStart = $start->copy();
                $duration = max($service->duration_minutes ?? 30, 15);

                while ($slotStart->lt($end)) {
                    $slotEnd = $slotStart->copy()->addMinutes($duration);
                    if ($slotEnd->gt($end)) {
                        break;
                    }

                    $slots->push([
                        'start' => $slotStart->copy(),
                        'end' => $slotEnd->copy(),
                        'timezone' => $rule->timezone,
                    ]);

                    $slotStart = $slotEnd->copy()->addMinutes($rule->buffer_after_minutes);
                }
            }
        }

        $slots = $slots->values();

        // Optionally trim by external calendars (placeholder: Google)
        if ($this->google && $service->provider) {
            $token = $service->provider->serviceCalendarTokens()->first();
            if ($token) {
                $busy = $this->google->fetchBusySlots($token, $from, $to);
                $slots = $slots->reject(function ($slot) use ($busy) {
                    return $busy->contains(function ($b) use ($slot) {
                        return $slot['start']->between($b['start'], $b['end']) || $slot['end']->between($b['start'], $b['end']);
                    });
                });
            }
        }

        return $slots;
    }
}
