<?php

namespace App\Controllers;

use App\Models\BookingGroupModel;
use App\Models\BookingModel;
use App\Models\CorporateAccountModel;
use App\Models\FolioModel;
use App\Models\GuestModel;
use App\Models\RatePlanModel;
use App\Models\RoomModel;
use App\Models\RoomTypeModel;
use App\Libraries\PhoneNumber;

/**
 * Group/bulk bookings — an organization or informal group reserving
 * several rooms for the same stay in one pass, instead of running the
 * single-room wizard (BookingWizardController) N times. See
 * CreateBookingGroupsTable's migration comment for the data model.
 *
 * Two-step flow, both plain request/response (no session state like the
 * single-room wizard uses — nothing is written to the database until
 * create()'s final submit, so there's nothing to "abandon"):
 *   1. new()/findRooms() — group details + desired room-type quantities
 *      for the stay -> real currently-available rooms matching those
 *      quantities, one guest-name/phone/email field per room.
 *   2. create() — creates the group, then one completely normal booking
 *      + folio per room (same real availability re-check immediately
 *      before each insert that the single wizard's confirm() does, since
 *      minutes may have passed since findRooms() rendered). A guest is
 *      matched by phone if one already exists, created otherwise — never
 *      duplicated for a name typed the same way twice.
 */
class BookingGroupController extends BaseController
{
    public function index()
    {
        $groups = new BookingGroupModel();
        $rows   = $groups->list();

        $bookings = new BookingModel();

        foreach ($rows as $row) {
            $row->roomCount = $bookings->where('booking_group_id', $row->id)->countAllResults();
        }

        return view('booking_groups/index', [
            'pageTitle' => 'Group Bookings',
            'groups'    => $rows,
        ]);
    }

    public function new()
    {
        return view('booking_groups/new', [
            'pageTitle'        => 'New Group Booking',
            'corporateAccounts' => (new CorporateAccountModel())->orderBy('company_name')->findAll(),
            'roomTypes'        => (new RoomTypeModel())->where('is_active', 1)->orderBy('base_rate')->findAll(),
        ]);
    }

    /**
     * Step 1 -> 2. Looks up real available rooms per requested type/
     * quantity and renders the guest-entry review page. Nothing is saved
     * yet — group details and the chosen room ids are carried forward as
     * hidden fields in that page's own form.
     */
    public function findRooms()
    {
        $groupName  = trim((string) $this->request->getPost('group_name'));
        $checkIn    = $this->request->getPost('check_in_date');
        $checkOut   = $this->request->getPost('check_out_date');
        $quantities = (array) $this->request->getPost('quantity');

        if ($groupName === '' || ! $checkIn || ! $checkOut || strtotime($checkOut) <= strtotime($checkIn)) {
            return redirect()->to('group-bookings/new')->withInput()->with('error', 'Enter a group name and a valid date range.');
        }

        $rooms       = new RoomModel();
        $ratePlans   = new RatePlanModel();
        $selected    = [];

        foreach ($quantities as $roomTypeId => $qty) {
            $qty = (int) $qty;

            if ($qty <= 0) {
                continue;
            }

            $available = $rooms->findAvailable($checkIn, $checkOut, (int) $roomTypeId);

            foreach (array_slice($available, 0, $qty) as $room) {
                $room->resolved_rate = $ratePlans->resolveRate((int) $room->room_type_id, $checkIn, $room->base_rate);
                $selected[]          = $room;
            }
        }

        if ($selected === []) {
            return redirect()->to('group-bookings/new')->withInput()->with('error', 'No rooms are available for that date range and quantity — try fewer rooms or different dates.');
        }

        $totalRequested = array_sum(array_map('intval', $quantities));

        return view('booking_groups/review', [
            'pageTitle'      => 'Review Group Booking',
            'groupName'      => $groupName,
            'corporateAccountId' => $this->request->getPost('corporate_account_id') ?: null,
            'organizerName'  => $this->request->getPost('organizer_name'),
            'organizerPhone' => $this->request->getPost('organizer_phone'),
            'organizerEmail' => $this->request->getPost('organizer_email'),
            'notes'          => $this->request->getPost('notes'),
            'checkIn'        => $checkIn,
            'checkOut'       => $checkOut,
            'rooms'          => $selected,
            'shortfall'      => $totalRequested - count($selected),
        ]);
    }

    /**
     * The actual commit. Re-checks each room's availability immediately
     * before booking it (same reasoning as BookingWizardController::
     * confirm() — the rooms shown in the review step could have been
     * booked by someone else in the meantime) rather than trusting the
     * hidden-field room ids blindly. A room that's no longer free is
     * silently skipped, not a fatal error for the whole group — the
     * success message reports exactly how many were actually booked.
     */
    public function create()
    {
        $groupModel = new BookingGroupModel();

        $checkIn  = $this->request->getPost('check_in_date');
        $checkOut = $this->request->getPost('check_out_date');
        $corporateAccountId = $this->request->getPost('corporate_account_id') ?: null;

        $groupData = [
            'group_name'           => $this->request->getPost('group_name'),
            'corporate_account_id' => $corporateAccountId,
            'organizer_name'       => $this->request->getPost('organizer_name') ?: null,
            'organizer_phone'      => $this->request->getPost('organizer_phone') ?: null,
            'organizer_email'      => $this->request->getPost('organizer_email') ?: null,
            'check_in_date'        => $checkIn,
            'check_out_date'       => $checkOut,
            'notes'                => $this->request->getPost('notes') ?: null,
            'created_by'           => $this->currentUserId(),
        ];

        if (! $groupModel->insert($groupData)) {
            return $this->redirectWithModelErrors($groupModel);
        }

        $groupId = $groupModel->getInsertID();

        $roomIds     = (array) $this->request->getPost('room_id');
        $guestNames  = (array) $this->request->getPost('guest_name');
        $guestPhones = (array) $this->request->getPost('guest_phone');
        $guestEmails = (array) $this->request->getPost('guest_email');

        $bookings = new BookingModel();
        $guests   = new GuestModel();
        $folios   = new FolioModel();

        $booked  = 0;
        $skipped = [];

        foreach ($roomIds as $i => $roomId) {
            $roomId = (int) $roomId;
            $name   = trim((string) ($guestNames[$i] ?? ''));
            $phone  = trim((string) ($guestPhones[$i] ?? ''));

            if ($name === '' || $phone === '') {
                continue; // staff left this room blank in the review step — not wanted after all
            }

            if ($bookings->hasOverlap($roomId, $checkIn, $checkOut)) {
                $skipped[] = "room #{$roomId} (booked by someone else in the meantime)";

                continue;
            }

            $normalizedPhone = PhoneNumber::normalize($phone) ?? $phone;
            $guest = $guests->where('phone', $normalizedPhone)->first();

            if ($guest === null) {
                $guestId = $guests->insert([
                    'full_name' => $name,
                    'phone'     => $phone,
                    'email'     => $guestEmails[$i] ?: null,
                ], true);

                if (! $guestId) {
                    $skipped[] = "room #{$roomId} (" . implode(' ', $guests->errors()) . ')';

                    continue;
                }
            } else {
                $guestId = $guest->id;
            }

            $room = (new RoomModel())->withType()->find($roomId);
            $rate = $room !== null ? (new RatePlanModel())->resolveRate((int) $room->room_type_id, $checkIn, $room->base_rate) : '0.00';

            $bookingId = $bookings->insert([
                'guest_id'             => $guestId,
                'room_id'              => $roomId,
                'check_in_date'        => $checkIn,
                'check_out_date'       => $checkOut,
                'status'               => 'confirmed',
                'source'               => 'group',
                'rate_applied'         => $rate,
                'corporate_account_id' => $corporateAccountId,
                'booking_group_id'     => $groupId,
            ], true);

            if (! $bookingId) {
                $skipped[] = "room #{$roomId} (" . implode(' ', $bookings->errors()) . ')';

                continue;
            }

            $folioId = $folios->openForBooking($bookingId, $guestId);

            if ($corporateAccountId) {
                $folios->setCorporateAccount($folioId, (int) $corporateAccountId);
            }

            $booked++;
        }

        if ($booked === 0) {
            $groupModel->delete($groupId);

            return redirect()->to('group-bookings/new')->with('error', 'No rooms could be booked — every selected room was taken in the meantime.');
        }

        $this->logActivity('bookings', "Created group booking \"{$groupData['group_name']}\" with {$booked} room(s).");

        $message = "Group booking created — {$booked} room(s) booked.";

        if ($skipped !== []) {
            $message .= ' Could not book: ' . implode(', ', $skipped) . '.';
        }

        return redirect()->to('group-bookings/' . $groupId)->with($skipped === [] ? 'success' : 'error', $message);
    }

    public function show(int $id)
    {
        $group = (new BookingGroupModel())->withCompany()->find($id);

        if ($group === null) {
            return redirect()->to('group-bookings')->with('error', 'Group booking not found.');
        }

        return view('booking_groups/show', [
            'pageTitle' => $group->group_name,
            'group'     => $group,
            'rooms'     => (new BookingModel())->forGroup($id),
        ]);
    }

    /** Cancels one room's booking without touching the rest of the group — same guard as BookingController::cancel(). */
    public function cancelRoom(int $groupId, int $bookingId)
    {
        $bookings = new BookingModel();
        $booking  = $bookings->find($bookingId);

        if ($booking === null || (int) $booking->booking_group_id !== $groupId) {
            return redirect()->to('group-bookings/' . $groupId)->with('error', 'Booking not found in this group.');
        }

        if (in_array($booking->status, ['checked_in', 'checked_out'], true)) {
            return redirect()->to('group-bookings/' . $groupId)->with('error', 'Cannot cancel a room that has already checked in.');
        }

        $bookings->update($bookingId, ['status' => 'cancelled']);
        $this->logActivity('bookings', "Cancelled room #{$bookingId} from group booking #{$groupId}.");

        return redirect()->to('group-bookings/' . $groupId)->with('success', 'Room cancelled.');
    }

    /** Cancels every still-cancellable room in the group (skips any already checked in/out) and the group itself. */
    public function cancel(int $id)
    {
        $groups = new BookingGroupModel();
        $group  = $groups->find($id);

        if ($group === null) {
            return redirect()->to('group-bookings')->with('error', 'Group booking not found.');
        }

        $bookings = new BookingModel();

        $bookings->whereIn('status', ['pending_deposit', 'confirmed'])
            ->where('booking_group_id', $id)
            ->set(['status' => 'cancelled'])
            ->update(null);

        $groups->update($id, ['status' => 'cancelled']);
        $this->logActivity('bookings', "Cancelled group booking \"{$group->group_name}\".");

        return redirect()->to('group-bookings/' . $id)->with('success', 'Group booking cancelled — any rooms already checked in were left as-is.');
    }
}
