<?php

namespace App\Controllers;

use App\Models\BookingModel;
use App\Models\GuestModel;
use App\Models\PaymentAttemptModel;
use App\Models\RatePlanModel;
use App\Models\RoomModel;
use App\Models\RoomTypeModel;
use RuntimeException;

/**
 * The app's first public, anonymous, cross-origin surface — a room-search
 * + book + pay-deposit flow meant to run inside an <iframe> embedded on a
 * hotel's own external website (see app/Views/settings/index.php's
 * "Booking Widget" tab for the embed snippet, and the CSRF-exception
 * comment in app/Config/Filters.php for why this route group is excluded
 * from the CSRF filter).
 *
 * Deliberately stateless — no session-backed wizard like
 * BookingWizardController's. Third-party/iframe cookie storage is
 * unreliable across browsers (Safari blocks it outright), so nothing here
 * can depend on session continuity between steps. Room type + dates are
 * carried as query string / hidden form fields instead, and re-validated
 * from scratch on every step server-side — a visitor can never trust a
 * stale or forged room/price/availability past what confirm() itself
 * re-checks at the moment of booking.
 *
 * Also deliberately narrower than the staff wizard: no guest search/
 * lookup (always creates a new guest — exposing a searchable guest
 * database to anonymous internet traffic would be a PII leak the staff
 * tool doesn't have to worry about), no services/extras step, no "pay
 * later" choice — a widget booking is always confirmed via an online
 * Hubtel deposit, computed server-side from billing.widget_deposit_percent
 * (never trusted from the client), never zero staff review in between.
 */
class WidgetBookingController extends BaseController
{
    /**
     * GET /widget/booking — the whole search experience: a date form,
     * plus (if check_in/check_out are present) the available room types
     * for those dates rendered on the same page. One page, one route,
     * no redirect hop between "search" and "results".
     */
    public function search()
    {
        $checkIn  = $this->request->getGet('check_in');
        $checkOut = $this->request->getGet('check_out');
        $roomTypes = [];

        if ($checkIn && $checkOut && $checkOut > $checkIn) {
            $roomTypes = $this->availableRoomTypes($checkIn, $checkOut);
        }

        return view('widget/search', [
            'pageTitle' => 'Book a Room',
            'checkIn'   => $checkIn,
            'checkOut'  => $checkOut,
            'roomTypes' => $roomTypes,
            'searched'  => (bool) ($checkIn && $checkOut),
        ]);
    }

    /**
     * GET /widget/booking/form — guest details + deposit summary for one
     * room type. room_type_id/check_in/check_out arrive as query string
     * (carried forward as hidden fields on submit) but are re-resolved
     * against the real room type + real availability here, never trusted
     * as-is from the URL.
     */
    public function bookForm()
    {
        $roomTypeId = (int) $this->request->getGet('room_type_id');
        $checkIn    = $this->request->getGet('check_in');
        $checkOut   = $this->request->getGet('check_out');

        $roomType = (new RoomTypeModel())->find($roomTypeId);

        if ($roomType === null || ! $checkIn || ! $checkOut || $checkOut <= $checkIn) {
            return redirect()->to('widget/booking')->with('error', 'Choose your dates and a room again.');
        }

        if (empty((new RoomModel())->findAvailable($checkIn, $checkOut, $roomTypeId))) {
            return redirect()->to('widget/booking?check_in=' . $checkIn . '&check_out=' . $checkOut)
                ->with('error', 'That room type just sold out for those dates — please pick another.');
        }

        $nights       = $this->nightsBetween($checkIn, $checkOut);
        $rate         = (new RatePlanModel())->resolveRate($roomType->id, $checkIn, $roomType->base_rate);
        $roomTotal    = money_multiply($rate, $nights);
        $depositPercent = (int) (service('settings')->get('billing.widget_deposit_percent') ?: 100);
        $depositAmount  = $this->depositAmountFor($roomTotal, $depositPercent);

        return view('widget/book_form', [
            'pageTitle'      => 'Your Details',
            'roomType'       => $roomType,
            'checkIn'        => $checkIn,
            'checkOut'       => $checkOut,
            'nights'         => $nights,
            'rate'           => $rate,
            'roomTotal'      => $roomTotal,
            'depositPercent' => $depositPercent,
            'depositAmount'  => $depositAmount,
        ]);
    }

    /**
     * POST /widget/booking/confirm — the sole write point. Re-validates
     * everything from scratch (mirrors BookingWizardController::confirm()'s
     * sequence: overlap re-check, guest create, booking insert, Hubtel
     * checkout request) rather than trusting anything carried in the POST
     * beyond room_type_id/dates/guest contact details.
     */
    public function confirm()
    {
        $roomTypeId = (int) $this->request->getPost('room_type_id');
        $checkIn    = $this->request->getPost('check_in');
        $checkOut   = $this->request->getPost('check_out');

        $roomType = (new RoomTypeModel())->find($roomTypeId);

        if ($roomType === null || ! $checkIn || ! $checkOut || $checkOut <= $checkIn) {
            return redirect()->to('widget/booking')->with('error', 'Something about your search was invalid — please start again.');
        }

        $available = (new RoomModel())->findAvailable($checkIn, $checkOut, $roomTypeId);

        if (empty($available)) {
            return redirect()->to('widget/booking?check_in=' . $checkIn . '&check_out=' . $checkOut)
                ->with('error', 'Sorry — that room type sold out while you were booking. Please pick another.');
        }

        $room = $available[0]; // first free physical room of this type — the widget never exposes room numbers to the public

        $guests = new GuestModel();
        $guestData = [
            'full_name' => $this->request->getPost('full_name'),
            'phone'     => $this->request->getPost('phone'),
            'email'     => $this->request->getPost('email') ?: null,
        ];

        if (! $guests->insert($guestData)) {
            return $this->redirectWithModelErrors($guests);
        }

        $guestId = $guests->getInsertID();
        $guest   = $guests->find($guestId);
        $this->logActivity('guest', 'Created guest "' . $guestData['full_name'] . '" via the online booking widget.');

        $nights = $this->nightsBetween($checkIn, $checkOut);
        $rate   = (new RatePlanModel())->resolveRate($roomType->id, $checkIn, $roomType->base_rate);

        $bookings = new BookingModel();
        $bookingData = [
            'guest_id'       => $guestId,
            'room_id'        => $room->id,
            'check_in_date'  => $checkIn,
            'check_out_date' => $checkOut,
            'status'         => 'pending_deposit',
            'source'         => 'online',
            'rate_applied'   => $rate,
            'adults'         => max(1, (int) $this->request->getPost('adults')),
            'children'       => max(0, (int) $this->request->getPost('children')),
        ];

        if (! $bookings->insert($bookingData)) {
            return $this->redirectWithModelErrors($bookings);
        }

        $bookingId = $bookings->getInsertID();
        $this->logActivity('bookings', "Booked room {$room->room_number} for {$checkIn} to {$checkOut} via the online widget.");

        $roomTotal      = money_multiply($rate, $nights);
        $depositPercent = (int) (service('settings')->get('billing.widget_deposit_percent') ?: 100);
        $depositAmount  = $this->depositAmountFor($roomTotal, $depositPercent);

        $checkoutUrl = $this->requestDeposit($bookingId, $depositAmount, $guest);

        if ($checkoutUrl === null) {
            return redirect()->to('widget/booking/confirmation/' . $bookingId)
                ->with('error', 'Your booking is held, but the payment link could not be generated — please contact the hotel directly.');
        }

        // Break out of the iframe entirely — Hubtel's hosted checkout is a
        // full third-party page not built to render in a small embedded
        // frame, and a guest shouldn't enter payment details in one.
        return view('widget/redirect_to_payment', ['checkoutUrl' => $checkoutUrl]);
    }

    /** GET /widget/booking/confirmation/(:num) — lands here after Hubtel (return or cancel), full-page, outside the iframe. */
    public function confirmation(int $bookingId)
    {
        $booking = (new BookingModel())->withDetails()->where('bookings.id', $bookingId)->first();

        if ($booking === null) {
            return redirect()->to('widget/booking')->with('error', 'Booking not found.');
        }

        return view('widget/confirmation', ['pageTitle' => 'Booking Status', 'booking' => $booking]);
    }

    /**
     * @return list<object{id: int, name: string, description: ?string, base_rate: string, capacity: int, amenities: ?string, photo_path: ?string, rate: string, available: int}>
     */
    private function availableRoomTypes(string $checkIn, string $checkOut): array
    {
        $available = (new RoomModel())->findAvailable($checkIn, $checkOut);
        $ratePlans = new RatePlanModel();

        $byType = [];

        foreach ($available as $room) {
            if (! isset($byType[$room->room_type_id])) {
                $byType[$room->room_type_id] = [
                    'id'          => (int) $room->room_type_id,
                    'name'        => $room->room_type_name,
                    'base_rate'   => $room->base_rate,
                    'capacity'    => $room->capacity,
                    'available'   => 0,
                    'rate'        => $ratePlans->resolveRate((int) $room->room_type_id, $checkIn, $room->base_rate),
                ];
            }

            $byType[$room->room_type_id]['available']++;
        }

        // Pull description/amenities/photo from the real room_types rows —
        // findAvailable()'s join only carries name/base_rate/capacity.
        $roomTypeModel = new RoomTypeModel();

        foreach ($byType as $id => &$row) {
            $full = $roomTypeModel->find($id);
            $row['description'] = $full->description ?? null;
            $row['amenities']   = $full->amenities ?? null;
            $row['photo_path']  = $full->photo_path ?? null;
        }

        return array_values($byType);
    }

    private function nightsBetween(string $checkIn, string $checkOut): int
    {
        return (int) max(1, (strtotime($checkOut) - strtotime($checkIn)) / 86400);
    }

    /** Same percentage-of-a-total shape as the tax computation in BookingWizardController::confirmationData(). */
    private function depositAmountFor(string $roomTotal, int $depositPercent): string
    {
        return number_format((float) $roomTotal * $depositPercent / 100, 2, '.', '');
    }

    /** @return ?string the Hubtel checkout URL, or null if the request failed */
    private function requestDeposit(int $bookingId, string $depositAmount, object $guest): ?string
    {
        $attempts   = new PaymentAttemptModel();
        $reference  = $attempts->generateClientReference('booking_deposit');
        $tenantSlug = service('tenantContext')->getTenant()->slug;

        try {
            $checkout = service('hubtelService')->createCheckout(
                (float) $depositAmount,
                "Booking #{$bookingId} deposit",
                $reference,
                site_url("webhooks/hubtel/checkout/{$tenantSlug}"),
                site_url("widget/booking/confirmation/{$bookingId}"),
                site_url("widget/booking/confirmation/{$bookingId}"),
            );
        } catch (RuntimeException $e) {
            log_message('error', 'Widget booking deposit checkout failed: ' . $e->getMessage());

            return null;
        }

        $attempts->insert([
            'purpose'          => 'booking_deposit',
            'booking_id'       => $bookingId,
            'client_reference' => $reference,
            'checkout_id'      => $checkout['checkoutId'],
            'checkout_url'     => $checkout['checkoutUrl'],
            'amount'           => $depositAmount,
            'status'           => 'pending',
        ]);

        service('messagingService')->send(
            'deposit_request',
            $guest->id,
            $guest->phone,
            "Hi {$guest->full_name}, please pay your deposit of " . format_money($depositAmount) .
                " to confirm your booking: {$checkout['checkoutUrl']}",
            [$guest->full_name, format_money($depositAmount), $checkout['checkoutUrl']],
        );

        return $checkout['checkoutUrl'];
    }
}
