<?php

namespace App\Http\Controllers\Public;

use App\Domain\Booking\BookingService;
use App\Domain\Booking\Exceptions\SlotUnavailableException;
use App\Domain\Booking\SlotEngine;
use App\Enums\AppointmentStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\PublicBookingRequest;
use App\Models\Appointment;
use App\Models\Dentist;
use App\Models\Patient;
use App\Support\ClinicTime;
use App\Support\Tenancy;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;

class PublicBookingController extends Controller
{
    public function index(Tenancy $tenancy)
    {
        return view('booking.index', [
            'tenant' => $tenancy->get(),
            'dentists' => Dentist::active()->where('is_bookable_online', true)
                ->orderBy('name')->get(),
        ]);
    }

    public function slots(Request $request, SlotEngine $engine)
    {
        $request->validate([
            'dentist_id' => ['required', 'integer'],
            'date' => ['required', 'date_format:Y-m-d', 'after_or_equal:today'],
        ]);

        $dentist = Dentist::active()->where('is_bookable_online', true)
            ->findOrFail($request->dentist_id);

        $slots = $engine->availableSlots(
            $dentist,
            CarbonImmutable::parse($request->date, ClinicTime::timezone()),
        );

        return response()->json([
            'data' => $slots->map(fn ($s) => [
                'label' => $s['label'],
                'starts_at_local' => $s['starts_at_utc']->setTimezone(ClinicTime::timezone())->format('Y-m-d H:i'),
            ])->all(),
        ]);
    }

    public function store(PublicBookingRequest $request, BookingService $booking, Tenancy $tenancy)
    {
        // Patients are identified by phone: returning patients are matched,
        // first-timers are created on the spot.
        $patient = Patient::query()
            ->where('phone_normalized', Patient::normalizePhone($request->phone))
            ->first()
            ?? Patient::create([
                'full_name' => $request->full_name,
                'phone' => $request->phone,
                'source' => 'online',
            ]);

        try {
            $appointment = $booking->book(
                dentist: Dentist::findOrFail($request->dentist_id),
                patient: $patient,
                startUtc: ClinicTime::toUtc($request->starts_at),
                source: 'online',
                reason: $request->reason,
            );
        } catch (SlotUnavailableException $e) {
            return back()->withInput()->with('error', $e->getMessage());
        }

        return redirect()->route('public.booking.confirmed', $appointment->uuid);
    }

    public function confirmed(Tenancy $tenancy, string $uuid)
    {
        $appointment = Appointment::with(['dentist', 'patient'])
            ->where('uuid', $uuid)->firstOrFail();

        return view('booking.confirmed', [
            'tenant' => $tenancy->get(),
            'appointment' => $appointment,
            'localStart' => $appointment->starts_at->setTimezone(ClinicTime::timezone()),
        ]);
    }

    public function cancel(Request $request, string $uuid)
    {
        $appointment = Appointment::where('uuid', $uuid)->firstOrFail();

        if ($appointment->status->canTransitionTo(AppointmentStatus::Cancelled)
            && $appointment->starts_at->isFuture()) {
            app(BookingService::class)->changeStatus(
                $appointment,
                AppointmentStatus::Cancelled,
                __('Cancelled by patient'),
            );
        }

        return redirect()->route('public.booking.index')
            ->with('status', __('Your appointment was cancelled.'));
    }
}
