<?php



namespace Modules\Ticket\Controllers\Api;



use App\Controllers\BaseController;

use CodeIgniter\API\ResponseTrait;

use Modules\Ticket\Config\TicketValidation;

use Modules\Ticket\Exceptions\TicketExceptions;

use Modules\Ticket\Models\TemporaryBook as TemporaryBookModel;

use Modules\Ticket\Models\MaxtimeModel as MaxtimeModel;

use CodeIgniter\Database\RawSql;

use CodeIgniter\I18n\Time;

use Modules\Ticket\Models\TicketModel;

use Modules\Trip\Models\SubtripModel;
use App\Libraries\BookingInventorySupport;
use App\Libraries\HoldSeatSupport;
use App\Libraries\TemporaryBookHoldService;
use App\Libraries\TripJourneyDateHelper;
use App\Libraries\SegmentSeatAvailability;



class TemporaryBook extends BaseController
{

    use ResponseTrait;



    /**

     * Temporary book model

     *

     * @var TemporaryBookModel

     */

    protected $temporaryBookModel;

    protected $maxtimeModel;

    protected $ticketModel;

    protected $subtripModel;

    protected $db;



    public function __construct()
    {

        // Init temporary book validator

        $this->ticketModel = new TicketModel();

        $ticketValidation = new TicketValidation;

        $this->validation = \Config\Services::validation($ticketValidation);

        $this->validation->setRuleGroup('temporarybooks');



        // Init temporary book model

        $this->temporaryBookModel = new TemporaryBookModel;

        $this->subtripModel = new SubtripModel();

        $this->maxtimeModel = new MaxtimeModel();

        $this->db = \Config\Database::connect();

    }



    public function checkSeats($book_from = "")
    {

        $updateBookId = null;

        $subtrip_id = $this->request->getVar('subtrip_id');

        $client_token = $this->request->getVar('ticket_token');

        $seat_names = $this->request->getVar('seat_names');

        if (is_array($seat_names)) {

            $seat_nameArr = array_map('trim', $seat_names);

        } else {

            $seat_nameArr = array_map('trim', array_filter(explode(',', $seat_names)));

        }



        $journey_date = $this->request->getVar('journey_date') ?? $this->request->getVar('journeydate') ?? egypt_today();

        sync_mysql_egypt_timezone();
        $subtrip = $subtrip_id ? $this->subtripModel->find($subtrip_id) : null;
        if (! is_object($subtrip)) {
            return $this->response->setJSON([
                'status' => 'failed',
                'response' => 422,
                'message' => 'Invalid subtrip_id.',
            ]);
        }

        $mainTripId = (int) $subtrip->trip_id;

        $journeyDateHelper = new TripJourneyDateHelper();
        $busStartDate = $journeyDateHelper->getBusStartDateForJourneylist((int) $mainTripId, (int) $subtrip_id, $journey_date, (int) $this->request->getVar('pickstand'));

        $mainTrip = $this->db->table('trips')->where('id', $mainTripId)->get()->getRow();

        if ($mainTrip && !empty($mainTrip->stoppage)) {
            $stoppageString = $mainTrip->stoppage;
        } else {
            $stoppageString = "";
        }

        $confirmedOverlaps = $this->getDynamicPickupDropCombinations(
            $subtrip->pick_location_id,
            $subtrip->drop_location_id,
            $stoppageString,
            $busStartDate,
            $mainTripId,
            (int) $subtrip_id,
            $journey_date
        );

        $bookedByOtherSeats = [];
        foreach ($confirmedOverlaps as $ticket) {
            $bookedByOtherSeats = array_merge($bookedByOtherSeats, array_map('trim', explode(',', $ticket['seatnumber'])));
        }

        $tempOverlaps = $this->getDynamicPickupDropCombinationsTemp(
            $subtrip->pick_location_id,
            $subtrip->drop_location_id,
            $stoppageString,
            $busStartDate,
            $subtrip_id,
            null,
            $journey_date
        );

        try {
            if (count($bookedByOtherSeats)) {
                $seatMatches = array_intersect(array_unique($bookedByOtherSeats), $seat_nameArr);
                if (count($seatMatches)) {
                    throw new TicketExceptions(sprintf('Seat %s already processed or occupied by overlapping segment!', implode(', ', $seatMatches)));
                }
            }

            $holdRow = $this->store(null, $book_from);

            if (empty($holdRow['persisted_committed'])) {
                throw new TicketExceptions(
                    'Seat hold was not committed to the database. Open GET hold-status/' . (int) ($holdRow['id'] ?? 0)
                    . ' and compare db_database with phpMyAdmin.'
                );
            }

        } catch (TicketExceptions $th) {
            return $this->response->setJSON([
                'status' => 'failed',
                'response' => 204,
                'message' => $th->getErrors()
            ]);
        }



        return $this->response->setJSON([

            'status' => 'success',

            'response' => 200,

            'message' => true,

            'data' => array_merge($holdRow, [
                'temporary_book_id' => $holdRow['id'] ?? null,
                'subtrip_id' => (int) $subtrip_id,
                'server_time_egypt' => egypt_now_sql(),
                'row_confirmed' => true,
                'phpmyadmin_hint' => 'Database "' . ($holdRow['db_database'] ?? '') . '" on host '
                    . ($holdRow['mysql_host'] ?? $holdRow['db_hostname'] ?? 'localhost')
                    . ' — run: SELECT * FROM temporarybooks WHERE id = ' . (int) ($holdRow['id'] ?? 0),
            ]),

        ]);

    }



    /**
     * Create or refresh a row in temporarybooks for this ticket_token + subtrip.
     *
     * @return array{id: int, expires_at: string, journey_date: string}
     */
    protected function store($seatBookId = null, $book_from = null): array
    {
        $subtrip_id = $this->request->getVar('subtrip_id');
        $journey_date_raw = $this->request->getVar('journey_date')
            ?? $this->request->getVar('journeydate')
            ?? egypt_today();

        $seatNamesRaw = $this->request->getVar('seat_names');
        if (is_array($seatNamesRaw)) {
            $seatNames = implode(',', array_map('trim', $seatNamesRaw));
        } else {
            $seatNames = trim((string) $seatNamesRaw);
        }

        $ticketToken = trim((string) $this->request->getVar('ticket_token'));
        if ($ticketToken === '') {
            throw new TicketExceptions('ticket_token is required.');
        }

        $subtrip = $subtrip_id ? $this->subtripModel->find($subtrip_id) : null;
        if (! is_object($subtrip)) {
            throw new TicketExceptions('Invalid subtrip_id.');
        }

        $mainTripId = (int) $subtrip->trip_id;

        $journeyDateHelper = new TripJourneyDateHelper();
        $busStartDate = $journeyDateHelper->getBusStartDateForJourneylist(
            $mainTripId,
            (int) $subtrip_id,
            (string) $journey_date_raw,
            (int) $this->request->getVar('pickstand')
        );

        if ($book_from === 'admin') {
            $maxtime = $this->maxtimeModel->where('book_from', $book_from)->first();
            $expiryMinutes = (int) ($maxtime->maxtime ?? 5);
        } else {
            $maxtime = $this->maxtimeModel->where('book_from', 'client')->first();
            $expiryMinutes = (int) ($maxtime->maxtime ?? 10);
        }
        if ($expiryMinutes < 1) {
            $expiryMinutes = 10;
        }

        $temporaryBookData = [
            'subtrip_id' => (int) $subtrip_id,
            'trip_id' => $mainTripId,
            'ticket_token' => $ticketToken,
            'seat_names' => $seatNames,
            'journey_date' => $busStartDate . ' 00:00:00',
            'expires_at' => egypt_add_minutes_sql($expiryMinutes),
        ];

        if (! $this->validation->run($temporaryBookData, 'temporarybooks')) {
            throw new TicketExceptions(null, 0, null, array_values($this->validation->getErrors()));
        }

        try {
            $holdService = new TemporaryBookHoldService();

            return $holdService->saveHold(
                (int) $subtrip_id,
                $mainTripId,
                $ticketToken,
                $seatNames,
                $busStartDate,
                $expiryMinutes
            );
        } catch (\RuntimeException $e) {
            throw new TicketExceptions($e->getMessage());
        }
    }

    /**
     * Delete only expired holds (optional: keep one id during the same request).
     */
    protected function cleanDatabase(?int $exceptHoldId = null): void
    {
        sync_mysql_egypt_timezone();
        $builder = $this->db->table('temporarybooks')->where('expires_at <', egypt_now_sql());
        if ($exceptHoldId !== null && $exceptHoldId > 0) {
            $builder->where('id !=', (int) $exceptHoldId);
        }
        $builder->delete();
    }



    protected function delete(int $seatBookId)
    {

        return $this->temporaryBookModel->where('id', $seatBookId)->delete();

    }

    /**
     * Debug: read a hold row with a new DB connection (proves commit vs phpMyAdmin).
     */
    public function holdStatus($holdId = 0)
    {
        $id = (int) $holdId;
        if ($id < 1) {
            return $this->response->setJSON([
                'status' => 'failed',
                'message' => 'Invalid hold id.',
            ]);
        }

        $persist = (new TemporaryBookHoldService())->confirmPersistedOnFreshConnection($id);

        return $this->response->setJSON([
            'status' => $persist['found'] ? 'success' : 'failed',
            'message' => $persist['found']
                ? 'Row exists in database (same data phpMyAdmin should show).'
                : 'Row not found — hold expired, wrong database, or never committed.',
            'data' => $persist,
        ]);
    }

    public function getHoldTime()
    {
        (new TemporaryBookHoldService())->cleanupExpiredHolds();

        $timeRow = $this->maxtimeModel
            ->select('maxtime')
            ->where('book_from', 'client') // ✅ correct CI4 where syntax
            ->first();

        return $this->response->setJSON([
            'status' => 'success',
            'response' => 200,
            'holdTime' => $timeRow ? $timeRow->maxtime : 10, // ✅ return actual value
        ]);
    }


    public function deleteHoldSeat(int $seatNumber)
    {

        $subtrip_id = $this->request->getVar('subtrip_id');



        if ($this->temporaryBookModel->where('seat_names', $seatNumber)->where('subtrip_id', $subtrip_id)->delete()) {

            return $this->response->setJSON([

                'status' => 'success',

                'response' => 200,

                'message' => true

            ]);

        } else {

            return $this->response->setJSON([

                'status' => 'failed',

                'response' => 204,

                'message' => "Seat not unHolded"

            ]);

        }



    }

    public function bookingcheck()
    {

        $updateBookId = null;

        $subtrip_id = $this->request->getVar('subtrip_id');

        $seat_names = $this->request->getVar('seat_names');

        $pickstand = $this->request->getVar('pickstand');

        $dropstand = $this->request->getVar('dropstand');

        $journeydate = $this->request->getVar('journeydate') ?? $this->request->getVar('journey_date');
        if (empty($journeydate)) {
            $journeydate = egypt_today();
        }

        if (is_array($seat_names)) {

            $seat_nameArr = array_map('trim', $seat_names);

        } else {

            $seat_nameArr = array_map('trim', array_filter(explode(',', $seat_names)));

        }



        $subtripInformation = $subtrip_id ? $this->subtripModel->find($subtrip_id) : null;

        if (!$subtripInformation || !is_object($subtripInformation)) {
            return $this->response->setJSON([
                'status' => 'error',
                'message' => 'Invalid subtrip information.'
            ]);
        }

        $mainTripId = $subtripInformation->trip_id;

        $mainTrip = $this->db->table('trips')->where('id', $mainTripId)->get()->getRow();
        $stoppageString = ($mainTrip && !empty($mainTrip->stoppage)) ? $mainTrip->stoppage : ($subtripInformation->stoppage ?? '');

        // Calculate the actual bus start date to handle cross-midnight trips.
        $journeyDateHelper = new TripJourneyDateHelper();
        $busStartDate = $journeyDateHelper->getBusStartDateForJourneylist((int) $mainTripId, (int) $subtrip_id, (string) $journeydate, (int) ($pickstand ?? 0));

        $ticket_token = $this->request->getVar('ticket_token');
        $TempBookedSeat = $this->getDynamicPickupDropCombinationsTemp(
            $subtripInformation->pick_location_id,
            $subtripInformation->drop_location_id,
            $stoppageString,
            $busStartDate,
            $subtripInformation->id,
            $ticket_token,
            $journeydate
        );

        $holdseats = [];

        if (!empty($TempBookedSeat)) {
            foreach ($TempBookedSeat as $holdRow) {
                $holdseats[] = $holdRow['seat_names'];
            }
        }

        // if($subtripInformation->id == 344){

        //     print_r($holdseats);

        //     exit;

        // }

        $results = $this->getDynamicPickupDropCombinations(
            $subtripInformation->pick_location_id,
            $subtripInformation->drop_location_id,
            $stoppageString,
            $busStartDate,
            $mainTripId,
            (int) $subtripInformation->id,
            $journeydate
        );

        $bookedSeats = array_column($results, 'seatnumber');

        $allBookedSeats = [];

        foreach ($holdseats as $holdseat) {

            $tempSeats = array_filter(explode(',', $holdseat));

            foreach ($tempSeats as $tempSeat) {

                $allBookedSeats[] = trim($tempSeat);

            }

        }

        foreach ($bookedSeats as $seatString) {

            $seats = array_filter(explode(',', $seatString));

            foreach ($seats as $seat) {
                $allBookedSeats[] = trim($seat);

            }

        }

        $departureDates = BookingInventorySupport::resolveDepartureDates(
            (int) $mainTripId,
            (int) $subtrip_id,
            $busStartDate,
            (string) $journeydate,
            (int) ($pickstand ?? 0)
        );
        foreach (HoldSeatSupport::getSegmentBlockedSeatNumbers(
            (int) $mainTripId,
            (int) $subtrip_id,
            $subtripInformation->pick_location_id,
            $subtripInformation->drop_location_id,
            $departureDates['all']
        ) as $blockedSeat) {
            $allBookedSeats[] = $blockedSeat;
        }

        $finalBookedSeats = array_unique($allBookedSeats);

        $requested_seats = array_map('trim', array_filter(explode(',', $seat_names)));

        $any_seat_matches = false;



        foreach ($requested_seats as $seat) {

            if (in_array($seat, $finalBookedSeats)) {

                $any_seat_matches = true;

                break;

            }

        }

        // if($subtripInformation->id == 344){

        //     var_dump($any_seat_matches);

        //     exit;

        // }



        if ($any_seat_matches) {

            $seatMsg = admin_bilingual_lang_strings('Localize.seat_already_booked_please_check');

            $response = [

                'status' => 'error',

                'message' => $seatMsg['en'],

                'message_ar' => $seatMsg['ar'],

            ];

        } else {

            $response = [
                'status' => 'success',
                'message' => 'Seats are available.'
            ];

        }



        return $this->response->setJSON($response);



    }

    public function getDynamicPickupDropCombinations($pickup, $drop, $tripSeries, $busStartDate, $tripId, $requestingSubtripId = null, $userJourneyDate = null)
    {
        $this->db = \Config\Database::connect();

        $stops = array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        if (empty($stops)) {
            return [];
        }

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $spanPick = $pickup;
        $spanDrop = $drop;

        if (!isset($positionMap[(string) $spanPick]) || !isset($positionMap[(string) $spanDrop])) {
            return [];
        }

        $departureDates = BookingInventorySupport::resolveDepartureDates(
            (int) $tripId,
            (int) ($requestingSubtripId ?? 0),
            (string) $busStartDate,
            $userJourneyDate
        );

        $builder = $this->db->table('tickets');
        $builder->select('tickets.*, subtrips.pick_location_id AS subtrip_pick_id, subtrips.drop_location_id AS subtrip_drop_id, subtrips.stoppage AS subtrip_stoppage');
        $builder->join('subtrips', 'tickets.subtrip_id = subtrips.id', 'LEFT');
        $builder->where('tickets.trip_id', $tripId);
        BookingInventorySupport::applyTicketJourneyDateFilter($builder, (int) $tripId, $departureDates['all']);
        $builder->where('tickets.cancel_status', 0);
        $builder->where('tickets.refund', 0);
        $tickets = $builder->get()->getResultArray();

        $overlappingTickets = [];

        foreach ($tickets as $ticket) {
            $segment = SegmentSeatAvailability::resolveBookingSegmentFromRow($stops, $ticket);
            if ($segment === null) {
                continue;
            }

            if (SegmentSeatAvailability::segmentsOverlap($stops, $spanPick, $spanDrop, $segment['pick'], $segment['drop'])) {
                $overlappingTickets[] = $ticket;
            }
        }

        return $overlappingTickets;
    }

    /**
     * @param array<int,string> $stops
     * @param array<string,mixed> $ticket
     * @return array{pick:string,drop:string}|null
     */
    private function resolveBookingSegmentForOverlap(array $stops, array $ticket): ?array
    {
        $candidates = [];

        $ticketPick = (string) ($ticket['pick_location_id'] ?? '');
        $ticketDrop = (string) ($ticket['drop_location_id'] ?? '');
        if ($ticketPick !== '' && $ticketDrop !== '') {
            $candidates[] = [$ticketPick, $ticketDrop];
        }

        $subPick = (string) ($ticket['subtrip_pick_id'] ?? '');
        $subDrop = (string) ($ticket['subtrip_drop_id'] ?? '');
        if ($subPick !== '' && $subDrop !== '') {
            $candidates[] = [$subPick, $subDrop];
        }

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        foreach ($candidates as [$pick, $drop]) {
            if (isset($positionMap[$pick], $positionMap[$drop]) && $positionMap[$pick] !== $positionMap[$drop]) {
                return ['pick' => $pick, 'drop' => $drop];
            }
        }

        $otherSub = (object) [
            'pick_location_id' => $subPick !== '' ? $subPick : $ticketPick,
            'drop_location_id' => $subDrop !== '' ? $subDrop : $ticketDrop,
            'stoppage' => (string) ($ticket['subtrip_stoppage'] ?? ''),
        ];

        return $this->expandSubtripToTripSpan($stops, $otherSub);
    }

    /**
     * Expand subtrip pick/drop (+ subtrip stoppage) to the full span on the main trip stop list,
     * so temporary holds block seats for the whole subtrip on the bus, not a thin slice.
     *
     * @param array<int,string> $stops Main trip stoppage in order
     * @param object|array|null $subtrip Subtrip row (pick_location_id, drop_location_id, stoppage)
     * @return array{pick:string,drop:string}|null
     */
    private function expandSubtripToTripSpan(array $stops, $subtrip): ?array
    {
        if (empty($stops) || $subtrip === null) {
            return null;
        }

        $pick = (string) (is_array($subtrip) ? ($subtrip['pick_location_id'] ?? '') : ($subtrip->pick_location_id ?? ''));
        $drop = (string) (is_array($subtrip) ? ($subtrip['drop_location_id'] ?? '') : ($subtrip->drop_location_id ?? ''));
        $stoppageRaw = is_array($subtrip) ? ($subtrip['stoppage'] ?? '') : ($subtrip->stoppage ?? '');
        $extra = array_filter(array_map('trim', explode(',', (string) $stoppageRaw)));

        $ids = array_unique(array_filter(array_merge([$pick, $drop], $extra)));

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $indices = [];
        foreach ($ids as $id) {
            if ($id !== '' && isset($positionMap[$id])) {
                $indices[] = $positionMap[$id];
            }
        }

        if (empty($indices)) {
            return null;
        }

        $minIdx = min($indices);
        $maxIdx = max($indices);

        return [
            'pick' => (string) $stops[$minIdx],
            'drop' => (string) $stops[$maxIdx],
        ];
    }

    public function getDynamicPickupDropCombinationsTemp($pickup, $drop, $tripSeries, $date, $subtripId, $ignoreToken = null, $userJourneyDate = null)
    {
        $this->db = \Config\Database::connect();

        $stops = array_values(array_filter(array_map('trim', explode(',', (string) $tripSeries))));
        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $reqSub = $subtripId ? $this->db->table('subtrips')->where('id', $subtripId)->get()->getRow() : null;
        $tripFilter = $reqSub ? (int) $reqSub->trip_id : 0;
        $spanPick = $pickup;
        $spanDrop = $drop;

        if (! SegmentSeatAvailability::canResolveSegment($stops, $spanPick, $spanDrop)) {
            return [];
        }

        $departureDates = BookingInventorySupport::resolveDepartureDates(
            $tripFilter,
            (int) $subtripId,
            (string) $date,
            $userJourneyDate
        );

        $currentTime = egypt_now_sql();

        $builder = $this->db->table('temporarybooks');
        $builder->select("temporarybooks.*, subtrips.pick_location_id, subtrips.drop_location_id, subtrips.stoppage AS existing_stoppage");
        $builder->join('subtrips', 'temporarybooks.subtrip_id = subtrips.id', 'LEFT');
        $builder->join('trips', 'subtrips.trip_id = trips.id', 'LEFT');
        BookingInventorySupport::applyTemporaryBookJourneyDateFilter($builder, $departureDates['all']);
        $builder->where('expires_at >', $currentTime);
        $builder->where('trips.status', 1);

        if ($reqSub) {
            $builder->where('subtrips.trip_id', $reqSub->trip_id);
        }

        if ($ignoreToken) {
            $builder->where('TRIM(temporarybooks.ticket_token) !=', trim($ignoreToken));
        }

        $tempBooks = $builder->get()->getResultArray();

        $overlappingTempBooks = [];

        foreach ($tempBooks as $tbook) {
            $segment = SegmentSeatAvailability::resolveBookingSegmentFromRow($stops, [
                'pick_location_id' => $tbook['pick_location_id'] ?? '',
                'drop_location_id' => $tbook['drop_location_id'] ?? '',
                'subtrip_pick_id' => $tbook['pick_location_id'] ?? '',
                'subtrip_drop_id' => $tbook['drop_location_id'] ?? '',
            ]);
            if ($segment === null) {
                continue;
            }

            if (SegmentSeatAvailability::segmentsOverlap($stops, $spanPick, $spanDrop, $segment['pick'], $segment['drop'])) {
                $overlappingTempBooks[] = $tbook;
            }
        }

        return $overlappingTempBooks;
    }

    private function stopIndexOnRoute(array $stops, $locationId): ?int
    {
        foreach ($stops as $idx => $stopId) {
            if ((string) $stopId === (string) $locationId) {
                return (int) $idx;
            }
        }

        return null;
    }

    /**
     * @return list<int>
     */
    private function traveledStopIndices(array $stops, $pick, $drop): array
    {
        $pickIdx = $this->stopIndexOnRoute($stops, $pick);
        $dropIdx = $this->stopIndexOnRoute($stops, $drop);
        if ($pickIdx === null || $dropIdx === null || $pickIdx === $dropIdx) {
            return [];
        }

        if ($pickIdx < $dropIdx) {
            return range($pickIdx, $dropIdx);
        }

        return range($dropIdx, $pickIdx);
    }

    private function segmentsOverlapInStops(array $stops, $pickup, $drop, $existingPickup, $existingDrop): bool
    {
        return SegmentSeatAvailability::segmentsOverlap($stops, $pickup, $drop, $existingPickup, $existingDrop);
    }

    private function getSegmentRange(array $stops, $pickup, $drop): ?array
    {
        if (empty($stops)) {
            return null;
        }

        $positionMap = [];
        foreach ($stops as $idx => $stopId) {
            $positionMap[(string) $stopId] = $idx;
        }

        $pickupKey = (string) $pickup;
        $dropKey = (string) $drop;
        if (!isset($positionMap[$pickupKey]) || !isset($positionMap[$dropKey])) {
            return null;
        }

        $min = min($positionMap[$pickupKey], $positionMap[$dropKey]);
        $max = max($positionMap[$pickupKey], $positionMap[$dropKey]);

        return $min === $max ? null : [$min, $max];
    }






}
