<?php

namespace App\Controllers;

use App\Models\BookingModel;
use App\Models\BookingServiceItemModel;
use App\Models\FolioLineItemModel;
use App\Models\FolioModel;
use App\Models\FolioPaymentModel;
use App\Models\GuestModel;
use App\Models\RoomModel;
use Config\Lookups;

/**
 * Check-in posts one folio line item per night at the booking's rate —
 * "date" is an explicit field on folio_line_items, so nightly charges are
 * itemized rather than one lump sum. This means an early/late checkout
 * doesn't automatically re-prorate what's already posted; staff adjust the
 * folio manually for that (flagged as an assumption — see Phase 1 summary).
 */
class CheckInController extends BaseController
{
    /**
     * Redesigned to match a user-supplied "Check-In" mockup. That mockup
     * showed Walk-in/Group Booking tabs, a 4-step wizard, a tax/fees
     * breakdown, and Quick Actions (print registration card, send a
     * pre-arrival email, ...) — none of which exist here: walk-ins are
     * already handled by the normal New Booking flow (source: Walk-in),
     * there's no group-booking concept, no tax tracking separate from a
     * folio's line items, no print templates, and no email infrastructure
     * (SMS/WhatsApp only — see App\Libraries\MessagingService). Rather
     * than build those, this stays a real 2-step flow (find the
     * reservation, confirm guest ID) with a "Find Reservation" search by
     * confirmation number added, since Upcoming Arrivals alone doesn't
     * cover a guest checking in for a stay that starts today but was
     * searched by number rather than picked from the list.
     */
    public function index()
    {
        $arrivals = (new BookingModel())->arrivalsOn(date('Y-m-d'));

        $q            = trim((string) $this->request->getGet('q'));
        $searchError  = null;

        if ($q !== '') {
            $id = null;

            if (preg_match('/^BK-0*(\d+)$/i', $q, $m)) {
                $id = (int) $m[1];
            } elseif (ctype_digit($q)) {
                $id = (int) $q;
            }

            $booking = $id !== null ? (new BookingModel())->find($id) : null;

            if ($booking !== null && $booking->status === 'confirmed') {
                return redirect()->to('checkin/' . $id);
            }

            $searchError = 'No confirmed reservation found for "' . $q . '".';
        }

        return view('checkin/index', [
            'pageTitle'    => 'Check-In',
            'pageSubtitle' => "Today's arrivals — " . date('j M Y'),
            'arrivals'     => $arrivals,
            'searchQuery'  => $q,
            'searchError'  => $searchError,
        ]);
    }

    public function form(int $bookingId)
    {
        $booking = (new BookingModel())->withDetails()->where('bookings.id', $bookingId)->first();

        if ($booking === null || $booking->status !== 'confirmed') {
            return redirect()->to('checkin')->with('error', 'Booking not found or already checked in.');
        }

        $guest  = (new GuestModel())->find($booking->guest_id);
        $nights = (int) max(1, (strtotime($booking->check_out_date) - strtotime($booking->check_in_date)) / 86400);

        return view('checkin/form', [
            'pageTitle'    => 'Check In',
            'pageSubtitle' => $booking->guest_name . ' — ' . sprintf('BK-%04d', $booking->id),
            'booking'      => $booking,
            'guest'        => $guest,
            'nights'       => $nights,
            'total'        => money_multiply($booking->rate_applied, $nights),
            'lookups'      => new Lookups(),
        ]);
    }

    public function process(int $bookingId)
    {
        $bookings = new BookingModel();
        $booking  = $bookings->find($bookingId);

        if ($booking === null || $booking->status !== 'confirmed') {
            return redirect()->to('checkin')->with('error', 'Booking not found or already checked in.');
        }

        $guests = new GuestModel();
        $guestUpdate = [
            'id_type'   => $this->request->getPost('id_type') ?: null,
            'id_number' => $this->request->getPost('id_number') ?: null,
        ];

        if (! $guests->update($booking->guest_id, $guestUpdate)) {
            return $this->redirectWithModelErrors($guests);
        }

        $bookings->update($bookingId, ['status' => 'checked_in']);

        $rooms = new RoomModel();
        $rooms->setStatus($booking->room_id, 'occupied');

        $folios   = new FolioModel();
        $folioId  = $folios->openForBooking($bookingId, $booking->guest_id);

        if (! empty($booking->corporate_account_id)) {
            $folios->setCorporateAccount($folioId, (int) $booking->corporate_account_id);
        }

        $this->postNightlyCharges($folioId, $booking);
        $this->postServiceItems($folioId, $booking);
        $this->applyInitialPayment($folioId, $booking);

        $this->logActivity('checkin', "Checked in booking #{$bookingId}.");

        return redirect()->to('checkin')->with('success', 'Guest checked in. Room marked occupied.');
    }

    private function postNightlyCharges(int $folioId, object $booking): void
    {
        $lineItems = new FolioLineItemModel();
        $date      = $booking->check_in_date;

        while ($date < $booking->check_out_date) {
            $lineItems->insert([
                'folio_id'    => $folioId,
                'description' => 'Room charge — ' . $date,
                'amount'      => $booking->rate_applied,
                'category'    => 'room_charge',
                'item_date'   => $date,
                'created_by'  => $this->currentUserId(),
            ]);

            $date = date('Y-m-d', strtotime($date . ' +1 day'));
        }
    }

    /**
     * Converts whatever was picked in the New Booking wizard's Add
     * Services step (see BookingServiceItemModel) into real folio
     * charges — the moment this runs is the first time a folio has
     * existed to post them to. Booking-service-item rows themselves are
     * left alone afterward as a record of what was originally selected.
     */
    private function postServiceItems(int $folioId, object $booking): void
    {
        $items = (new BookingServiceItemModel())->forBooking($booking->id);

        if (empty($items)) {
            return;
        }

        $lineItems = new FolioLineItemModel();

        foreach ($items as $item) {
            $lineItems->insert([
                'folio_id'    => $folioId,
                'description' => $item->service_name . ($item->quantity > 1 ? ' x' . $item->quantity : ''),
                'amount'      => money_multiply($item->unit_price, $item->quantity),
                'category'    => 'other',
                'item_date'   => $booking->check_in_date,
                'created_by'  => $this->currentUserId(),
            ]);
        }
    }

    /**
     * A payment collected in person when the booking was made (see
     * BookingWizardController::confirm() and the migration's doc
     * comment) becomes a real folio_payments row here — dated to when it
     * was actually collected, not to today. `initial_payment_applied`
     * guards against double-posting if check-in were ever re-run.
     */
    private function applyInitialPayment(int $folioId, object $booking): void
    {
        if (empty($booking->initial_payment_amount) || (float) $booking->initial_payment_amount <= 0 || $booking->initial_payment_applied) {
            return;
        }

        (new FolioPaymentModel())->insert([
            'folio_id'    => $folioId,
            'amount'      => $booking->initial_payment_amount,
            'method'      => $booking->initial_payment_method ?: 'cash',
            'reference'   => 'Collected at booking',
            'recorded_by' => $this->currentUserId(),
            'paid_at'     => $booking->created_at,
        ]);

        (new BookingModel())->update($booking->id, ['initial_payment_applied' => 1]);
    }
}
