<?php

namespace App\Http\Controllers;

use App\Http\Requests\CancelPublicBookingRequest;
use App\Http\Requests\ReschedulePublicBookingRequest;
use App\Http\Requests\StorePublicBookingDetailsRequest;
use App\Models\Appointment;
use App\Services\Booking\BookingService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;

class PublicBookingController extends Controller
{
    public function __construct(private readonly BookingService $bookingService)
    {
    }

    public function profile(string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $services = $this->bookingService->activeServices($business);

        return view('public.booking.profile', [
            ...$this->baseData($business_slug),
            'services' => $this->bookingService->serviceCards($services->take(3)),
            'servicesCount' => $services->count(),
        ]);
    }

    public function services(string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);

        return view('public.booking.services', [
            ...$this->baseData($business_slug),
            'services' => $this->bookingService->serviceCards($this->bookingService->activeServices($business)),
        ]);
    }

    public function staff(Request $request, string $business_slug): View|RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $serviceId = $request->integer('service_id');

        if (! $serviceId) {
            return redirect()->route('public.booking.services', $business_slug);
        }

        $service = $this->bookingService->service($business, $serviceId);
        $staff = $this->bookingService->availableStaff($business, $service);

        return view('public.booking.staff', [
            ...$this->baseData($business_slug, $service),
            'service' => $service,
            'staff' => $this->bookingService->staffCards($staff),
        ]);
    }

    public function dateTime(Request $request, string $business_slug): View|RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $serviceId = $request->integer('service_id');

        if (! $serviceId) {
            return redirect()->route('public.booking.services', $business_slug);
        }

        $service = $this->bookingService->service($business, $serviceId);
        $staff = $this->bookingService->staff($business, $request->input('staff_profile_id', 'any'), $service);
        $selectedDate = Carbon::parse($request->input('date', today()->toDateString()))->startOfDay();

        return view('public.booking.date-time', [
            ...$this->baseData($business_slug, $service, $staff, $selectedDate->toDateString()),
            'service' => $service,
            'staffMember' => $staff,
            'dateOptions' => $this->bookingService->dateOptions($business, $service, $staff),
            'slotGroups' => $this->bookingService->groupedSlots($business, $service, $staff, $selectedDate),
            'selectedDate' => $selectedDate,
        ]);
    }

    public function details(Request $request, string $business_slug): View|RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $selection = $this->selectionFromRequest($request);

        if (! $selection) {
            return redirect()->route('public.booking.services', $business_slug);
        }

        $service = $this->bookingService->service($business, (int) $selection['service_id']);
        $staff = $this->bookingService->staff($business, (int) $selection['staff_profile_id'], $service);

        return view('public.booking.details', [
            ...$this->baseData($business_slug, $service, $staff, $selection['appointment_date'], $selection['appointment_time']),
            'service' => $service,
            'staffMember' => $staff,
            'selection' => $selection,
            'customerDetails' => session($this->sessionKey($business_slug).'.customer', []),
        ]);
    }

    public function storeDetails(StorePublicBookingDetailsRequest $request, string $business_slug): RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $validated = $request->validated();
        $service = $this->bookingService->service($business, (int) $validated['service_id']);
        $staff = $this->bookingService->staff($business, (int) $validated['staff_profile_id'], $service);

        session()->put($this->sessionKey($business_slug), [
            'selection' => [
                'service_id' => $service->id,
                'staff_profile_id' => $staff->id,
                'appointment_date' => $validated['appointment_date'],
                'appointment_time' => $validated['appointment_time'],
            ],
            'customer' => [
                'customer_name' => $validated['customer_name'],
                'customer_phone' => $validated['customer_phone'],
                'customer_email' => $validated['customer_email'] ?? null,
                'notes' => $validated['notes'] ?? null,
            ],
        ]);

        return redirect()->route('public.booking.summary', $business_slug);
    }

    public function summary(string $business_slug): View|RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $session = session($this->sessionKey($business_slug));

        if (! $session || empty($session['selection']) || empty($session['customer'])) {
            return redirect()->route('public.booking.services', $business_slug);
        }

        $service = $this->bookingService->service($business, (int) $session['selection']['service_id']);
        $staff = $this->bookingService->staff($business, (int) $session['selection']['staff_profile_id'], $service);

        return view('public.booking.summary', [
            ...$this->baseData($business_slug, $service, $staff, $session['selection']['appointment_date'], $session['selection']['appointment_time'], $session['customer']),
            'selection' => $session['selection'],
            'customerDetails' => $session['customer'],
        ]);
    }

    public function confirm(string $business_slug): RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $sessionKey = $this->sessionKey($business_slug);
        $session = session($sessionKey);

        if (! $session || empty($session['selection']) || empty($session['customer'])) {
            return redirect()->route('public.booking.services', $business_slug);
        }

        try {
            $appointment = $this->bookingService->createBooking($business, $session['selection'], $session['customer']);
        } catch (ValidationException $exception) {
            session()->put($sessionKey.'.failed', $session);

            return redirect()
                ->route('public.booking.failed', $business_slug)
                ->withErrors($exception->errors());
        }

        session()->forget($sessionKey);

        return redirect()->route('public.booking.confirmation', [
            'business_slug' => $business_slug,
            'token' => $appointment->reschedule_token,
        ]);
    }

    public function confirmation(Request $request, string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $appointment = $request->query('token')
            ? $this->bookingService->appointmentByToken($business, (string) $request->query('token'), 'reschedule')
            : null;

        return view('public.booking.confirmation', [
            ...$this->baseData(
                $business_slug,
                $appointment?->service,
                $appointment?->staffProfile,
                $appointment?->starts_at?->toDateString(),
                $appointment?->starts_at?->format('H:i'),
                null,
                $appointment
            ),
            'appointment' => $appointment,
        ]);
    }

    public function failed(string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $session = session($this->sessionKey($business_slug).'.failed');
        $service = null;
        $staff = null;
        $suggestion = null;

        if ($session && ! empty($session['selection'])) {
            $service = $this->bookingService->service($business, (int) $session['selection']['service_id']);
            $staff = $this->bookingService->staff($business, (int) $session['selection']['staff_profile_id'], $service);
            $suggestion = $this->bookingService->nextAvailableSuggestion($business, $service, $staff);
        }

        return view('public.booking.failed', [
            ...$this->baseData($business_slug, $service, $staff),
            'suggestion' => $suggestion,
            'service' => $service,
            'staffMember' => $staff,
        ]);
    }

    public function reschedule(Request $request, string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $appointment = $this->bookingService->appointmentByToken($business, (string) $request->query('token'), 'reschedule');

        return view('public.booking.reschedule', [
            ...$this->baseData($business_slug, $appointment->service, $appointment->staffProfile, null, null, null, $appointment),
            'appointment' => $appointment,
            'token' => $request->query('token'),
            'dateOptions' => $this->bookingService->dateOptions($business, $appointment->service, $appointment->staffProfile),
            'slotGroups' => $this->bookingService->groupedSlots($business, $appointment->service, $appointment->staffProfile, today()),
        ]);
    }

    public function updateReschedule(ReschedulePublicBookingRequest $request, string $business_slug): RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $appointment = $this->bookingService->appointmentByToken($business, (string) $request->query('token'), 'reschedule');

        try {
            $appointment = $this->bookingService->reschedule($business, $appointment, $request->validated());
        } catch (ValidationException $exception) {
            return redirect()
                ->back()
                ->withInput()
                ->withErrors($exception->errors());
        }

        return redirect()->route('public.booking.confirmation', [
            'business_slug' => $business_slug,
            'token' => $appointment->reschedule_token,
        ])->with('status', 'Appointment rescheduled successfully.');
    }

    public function cancel(Request $request, string $business_slug): View
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $appointment = $this->bookingService->appointmentByToken($business, (string) $request->query('token'), 'cancel');

        return view('public.booking.cancel', [
            ...$this->baseData($business_slug, $appointment->service, $appointment->staffProfile, null, null, null, $appointment),
            'appointment' => $appointment,
            'token' => $request->query('token'),
        ]);
    }

    public function cancelStore(CancelPublicBookingRequest $request, string $business_slug): RedirectResponse
    {
        $business = $this->bookingService->businessBySlug($business_slug);
        $appointment = $this->bookingService->appointmentByToken($business, (string) $request->query('token'), 'cancel');

        $appointment = $this->bookingService->cancel(
            $business,
            $appointment,
            $request->validated('cancel_reason'),
            $request->validated('cancel_note')
        );

        return redirect()->route('public.booking.confirmation', [
            'business_slug' => $business_slug,
            'token' => $appointment->reschedule_token,
        ])->with('status', 'Appointment cancelled successfully.');
    }

    private function baseData(string $businessSlug, mixed $service = null, mixed $staff = null, ?string $date = null, ?string $time = null, ?array $customer = null, ?Appointment $appointment = null): array
    {
        $business = $this->bookingService->businessBySlug($businessSlug);

        return [
            'businessModel' => $business,
            'businessSlug' => $businessSlug,
            'business' => $this->bookingService->businessView($business),
            'selected' => $this->bookingService->selectedSummary($business, $service, $staff, $date, $time, $customer, $appointment),
        ];
    }

    private function selectionFromRequest(Request $request): ?array
    {
        if (! $request->filled(['service_id', 'staff_profile_id', 'date', 'time'])) {
            return null;
        }

        return [
            'service_id' => $request->input('service_id'),
            'staff_profile_id' => $request->input('staff_profile_id'),
            'appointment_date' => $request->input('date'),
            'appointment_time' => $request->input('time'),
        ];
    }

    private function sessionKey(string $businessSlug): string
    {
        return 'public_booking.'.$businessSlug;
    }
}
