<?php

namespace App\Http\Controllers;

use App\Enums\BookingStatus;
use App\Http\Requests\ConfirmBookingRequest;
use App\Http\Requests\RescheduleBookingRequest;
use App\Http\Requests\StoreBookingRequest;
use App\Http\Requests\UpdateBookingRequest;
use App\Models\Booking;
use App\Models\Customer;
use App\Models\EventType;
use App\Models\Space;
use App\Models\User;
use App\Services\AuditService;
use App\Services\BookingService;
use App\Services\BookingStatusService;
use DomainException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class BookingController extends Controller
{
    public function index(Request $request): View
    {
        $this->authorize('viewAny', Booking::class);

        $bookings = Booking::query()
            ->with(['customer', 'coordinator'])
            ->when($request->filled('search'), function ($query) use ($request): void {
                $search = $request->string('search')->toString();
                $query->where(function ($builder) use ($search): void {
                    $builder->where('booking_number', 'like', "%{$search}%")
                        ->orWhere('event_name', 'like', "%{$search}%")
                        ->orWhere('contact_name', 'like', "%{$search}%")
                        ->orWhereHas('customer', fn ($q) => $q->where('name', 'like', "%{$search}%"));
                });
            })
            ->when($request->filled('view') || $request->filled('filter'), function ($query) use ($request): void {
                $view = $request->string('view')->toString() ?: $request->string('filter')->toString();
                $today = now()->startOfDay();
                $requiresIssuedInvoice = in_array($view, ['upcoming', 'today', 'past'], true);

                if ($requiresIssuedInvoice) {
                    $query->hasIssuedInvoice();
                }

                match ($view) {
                    'upcoming' => $query->where('event_start', '>=', $today)
                        ->whereNotIn('status', [BookingStatus::Cancelled, BookingStatus::Completed, BookingStatus::NoShow]),
                    'today' => $query->whereDate('event_start', $today)
                        ->whereNotIn('status', [BookingStatus::Cancelled, BookingStatus::Completed, BookingStatus::NoShow]),
                    'past' => $query->where('event_end', '<', $today),
                    'provisional' => $query->where('status', BookingStatus::Provisional),
                    'confirmed' => $query->where('status', BookingStatus::Confirmed),
                    'cancelled' => $query->where('status', BookingStatus::Cancelled),
                    default => null,
                };
            })
            ->when($request->filled('status'), fn ($query) => $query->where('status', $request->string('status')))
            ->latest('event_start')
            ->paginate(15)
            ->withQueryString();

        return view('bookings.index', compact('bookings'));
    }

    public function create(): RedirectResponse
    {
        $this->authorize('create', Booking::class);

        return redirect()
            ->route('invoices.create')
            ->with('info', 'Create an invoice to reserve spaces. The booking is confirmed automatically after the first payment.');
    }

    public function store(StoreBookingRequest $request, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('create', Booking::class);

        try {
            $booking = $bookingService->create($request->validated());
        } catch (DomainException $exception) {
            return back()->withInput()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking created successfully.');
    }

    public function show(Booking $booking): View
    {
        $this->authorize('view', $booking);

        $booking->load([
            'customer',
            'eventType',
            'coordinator',
            'spaces.space',
            'spaces.space.layouts',
            'items.product',
            'items.package',
            'invoices.items',
            'attachments.uploader',
        ]);

        $primaryInvoice = $booking->invoices
            ->filter(fn ($invoice) => $invoice->status !== \App\Enums\InvoiceStatus::Void)
            ->sortByDesc('id')
            ->first();

        if ($primaryInvoice !== null) {
            app(\App\Services\BookingService::class)->syncFinancialsFromPrimaryInvoice($booking);
            $booking->refresh();
        }

        return view('bookings.show', compact('booking', 'primaryInvoice'));
    }

    public function edit(Booking $booking): View
    {
        $this->authorize('update', $booking);

        $booking->load(['spaces.space', 'items']);
        $customers = Customer::query()->where('is_active', true)->orderBy('name')->get();
        $spaces = Space::query()->orderBy('name')->get();
        $eventTypes = EventType::query()->where('is_active', true)->orderBy('sort_order')->get();
        $coordinators = User::query()->where('is_active', true)->orderBy('name')->get();

        return view('bookings.edit', compact('booking', 'customers', 'spaces', 'eventTypes', 'coordinators'));
    }

    public function update(UpdateBookingRequest $request, Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('update', $booking);

        try {
            $bookingService->update($booking, $request->validated());
        } catch (DomainException $exception) {
            return back()->withInput()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking updated successfully.');
    }

    public function destroy(Booking $booking, AuditService $auditService): RedirectResponse
    {
        $this->authorize('delete', $booking);

        $oldValues = $booking->toArray();
        $booking->delete();

        $auditService->log('booking.archived', $booking, $oldValues);

        return redirect()
            ->route('bookings.index')
            ->with('success', 'Booking archived successfully.');
    }

    public function confirm(ConfirmBookingRequest $request, Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('confirm', $booking);

        try {
            $bookingService->confirm($booking, $request->validated('reason'));
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking confirmed successfully.');
    }

    public function cancel(Request $request, Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('cancel', $booking);

        $request->validate(['reason' => ['nullable', 'string', 'max:1000']]);

        try {
            $bookingService->cancel($booking, $request->string('reason')->toString() ?: null);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking cancelled successfully.');
    }

    public function postpone(Request $request, Booking $booking, BookingStatusService $statusService): RedirectResponse
    {
        $this->authorize('update', $booking);

        $request->validate(['reason' => ['nullable', 'string', 'max:1000']]);

        try {
            $statusService->transition($booking, BookingStatus::Postponed, $request->string('reason')->toString() ?: null);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking marked as postponed.');
    }

    public function markInProgress(Booking $booking, BookingStatusService $statusService): RedirectResponse
    {
        $this->authorize('update', $booking);

        try {
            $statusService->transition($booking, BookingStatus::InProgress);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking marked as in progress.');
    }

    public function markCompleted(Booking $booking, BookingStatusService $statusService): RedirectResponse
    {
        $this->authorize('update', $booking);

        try {
            $statusService->transition($booking, BookingStatus::Completed);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking marked as completed.');
    }

    public function markNoShow(Booking $booking, BookingStatusService $statusService): RedirectResponse
    {
        $this->authorize('update', $booking);

        try {
            $statusService->transition($booking, BookingStatus::NoShow);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking marked as no show.');
    }

    public function placeHold(Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('placeHold', $booking);

        try {
            $bookingService->placeHold($booking);
        } catch (DomainException $exception) {
            return back()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Hold placed successfully.');
    }

    public function reschedule(RescheduleBookingRequest $request, Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('reschedule', $booking);

        try {
            $bookingService->reschedule($booking, $request->validated());
        } catch (DomainException $exception) {
            return back()->withInput()->with('error', $exception->getMessage());
        }

        return redirect()
            ->route('bookings.show', $booking)
            ->with('success', 'Booking rescheduled successfully.');
    }

    public function duplicate(Booking $booking, BookingService $bookingService): RedirectResponse
    {
        $this->authorize('duplicate', $booking);

        $duplicate = $bookingService->duplicate($booking);

        return redirect()
            ->route('bookings.show', $duplicate)
            ->with('success', 'Booking duplicated successfully.');
    }
}
