<?php

namespace App\Libraries;

use CodeIgniter\Database\BaseConnection;
use Modules\Ticket\Models\TicketModel;
use Modules\Trip\Models\SubtripModel;

/**
 * Segment-aware seat inventory for the same physical bus (main trip stoppage).
 *
 * Primary use case — subtrip ticket booking: load all tickets on trip_id (any subtrip),
 * compare each ticket's pick/drop to the requested segment via SegmentSeatAvailability.
 * Same seat can be sold again when journeys only meet at a handoff stop.
 *
 * Also merges checkout temporarybooks (same overlap rule) and admin hold_seats (separate
 * product feature: journey date + Egypt time window; optional segment filter for display).
 */
class SegmentSeatInventoryService
{
    protected BaseConnection $db;

    protected SubtripModel $subtripModel;

    protected TicketModel $ticketModel;

    public function __construct(?BaseConnection $db = null)
    {
        $this->db = $db ?? \Config\Database::connect();
        $this->subtripModel = new SubtripModel();
        $this->ticketModel = new TicketModel();
    }

    /**
     * @return array{
     *   booked: list<string>,
     *   hold: list<string>,
     *   block: list<string>,
     *   boarding_date: string,
     *   bus_start_date: string,
     *   trip_id: int,
     *   subtrip_id: int,
     *   pick: string|int,
     *   drop: string|int,
     *   stops: list<string>
     * }
     */
    public function collectSeatBuckets(
        int $subtripId,
        string $journeyDateParam,
        int $pickStandId = 0,
        ?string $ignoreTempToken = null,
        bool $cleanExpired = true,
        $requestedPick = null,
        $requestedDrop = null,
        bool $userDateIsServiceDay = false
    ): array {
        BookingInventorySupport::syncBookingTimezone();

        $empty = [
            'booked' => [],
            'hold' => [],
            'block' => [],
            'boarding_date' => '',
            'bus_start_date' => '',
            'trip_id' => 0,
            'subtrip_id' => $subtripId,
            'pick' => '',
            'drop' => '',
            'stops' => [],
        ];

        $subtrip = $this->subtripModel->find($subtripId);
        if (! $subtrip) {
            return $empty;
        }

        if ($cleanExpired) {
            $this->cleanExpiredTemporaryBooks($ignoreTempToken);
            HoldSeatSupport::expireStaleAdminHolds($this->db);
        }

        $tripId = (int) $subtrip->trip_id;
        $pick   = $requestedPick !== null && $requestedPick !== '' ? $requestedPick : $subtrip->pick_location_id;
        $drop   = $requestedDrop !== null && $requestedDrop !== '' ? $requestedDrop : $subtrip->drop_location_id;

        if ($pickStandId <= 0) {
            $pickStandId = BookingInventorySupport::resolveDefaultPickStandId($tripId, $subtripId);
        }

        if ($userDateIsServiceDay) {
            $departureDates = BookingInventorySupport::resolveDepartureDates(
                $tripId,
                $subtripId,
                $journeyDateParam,
                null,
                $pickStandId
            );
        } else {
            $departureDates = BookingInventorySupport::resolveSearchDepartureDates(
                $tripId,
                $subtripId,
                $journeyDateParam,
                $pickStandId
            );
        }
        $busStartDate         = $departureDates['bus_start'];
        $boardingCalendarDate = $departureDates['boarding'];

        $stops = SegmentSeatAvailability::parseStoppage(
            BookingInventorySupport::resolveTripStoppageForOverlap($tripId, $subtripId)
        );

        if ($stops === [] || ! SegmentSeatAvailability::canResolveSegment($stops, $pick, $drop)) {
            return array_merge($empty, [
                'boarding_date' => $boardingCalendarDate,
                'bus_start_date' => $busStartDate,
                'trip_id' => $tripId,
                'subtrip_id' => $subtripId,
                'pick' => $pick,
                'drop' => $drop,
                'stops' => $stops,
            ]);
        }

        $requestStoppage = trim((string) ($subtrip->stoppage ?? ''));

        $booked = $this->collectBookedSeatNumbers(
            $tripId,
            $subtripId,
            $stops,
            $pick,
            $drop,
            $departureDates,
            $requestStoppage
        );

        $hold = $this->collectTempHoldSeatNumbers(
            $tripId,
            $subtripId,
            $stops,
            $pick,
            $drop,
            $departureDates,
            $ignoreTempToken,
            $requestStoppage
        );

        $block = $this->collectAdminBlockedSeatNumbers(
            $tripId,
            $subtripId,
            $stops,
            $pick,
            $drop,
            $departureDates
        );

        return [
            'booked' => $booked,
            'hold' => $hold,
            'block' => $block,
            'boarding_date' => $boardingCalendarDate,
            'bus_start_date' => $busStartDate,
            'trip_id' => $tripId,
            'subtrip_id' => $subtripId,
            'pick' => $pick,
            'drop' => $drop,
            'stops' => $stops,
        ];
    }

    /**
     * @return list<string>
     */
    public function collectUnavailableSeatNumbers(
        int $subtripId,
        string $journeyDateParam,
        int $pickStandId = 0,
        ?string $ignoreTempToken = null,
        $requestedPick = null,
        $requestedDrop = null,
        bool $userDateIsServiceDay = false
    ): array {
        $buckets = $this->collectSeatBuckets(
            $subtripId,
            $journeyDateParam,
            $pickStandId,
            $ignoreTempToken,
            true,
            $requestedPick,
            $requestedDrop,
            $userDateIsServiceDay
        );

        return array_values(array_unique(array_merge(
            $buckets['booked'],
            $buckets['hold'],
            $buckets['block']
        )));
    }

    /**
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return list<string>
     */
    public function collectBookedSeatNumbers(
        int $tripId,
        int $subtripId,
        array $stops,
        $requestedPick,
        $requestedDrop,
        array $departureDates,
        ?string $requestedSubtripStoppage = null
    ): array {
        if ($tripId <= 0 || $stops === []) {
            return [];
        }

        $builder = $this->ticketModel->builder();
        $builder->select(
            'tickets.subtrip_id, tickets.pick_location_id, tickets.drop_location_id, tickets.seatnumber, '
            . 'tickets.journeydata, tickets.pick_stand_id, subtrips.pick_location_id AS subtrip_pick_id, '
            . 'subtrips.drop_location_id AS subtrip_drop_id, subtrips.stoppage AS subtrip_stoppage, '
            . '(SELECT MIN(DATE(jl.journeydate)) FROM journeylists jl '
            . 'WHERE jl.booking_id = tickets.booking_id AND jl.deleted_at IS NULL) AS manifest_journeydate',
            false
        )
            ->join('subtrips', 'tickets.subtrip_id = subtrips.id', 'LEFT')
            ->join('trips', 'tickets.trip_id = trips.id', 'LEFT')
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0)
            ->where('trips.status', 1)
            ->where('tickets.trip_id', $tripId);

        BookingInventorySupport::applyTicketJourneyDateFilter($builder, $tripId, $departureDates);

        $seats = [];
        $busStart = $departureDates['bus_start'];
        foreach ($builder->get()->getResultArray() as $ticket) {
            $ticketSubtripId = (int) ($ticket['subtrip_id'] ?? 0) ?: $subtripId;
            if (! BookingInventorySupport::ticketBelongsToBusRun(
                $tripId,
                $ticketSubtripId,
                (string) ($ticket['journeydata'] ?? ''),
                (int) ($ticket['pick_stand_id'] ?? 0),
                $busStart,
                isset($ticket['manifest_journeydate']) ? (string) $ticket['manifest_journeydate'] : null
            )) {
                continue;
            }

            $segment = SegmentSeatAvailability::resolveBookingSegmentFromRow($stops, $ticket);
            if ($segment === null) {
                continue;
            }
            if (! SegmentSeatAvailability::segmentsOverlapWithPath(
                $stops,
                $requestedPick,
                $requestedDrop,
                $requestedSubtripStoppage,
                $segment['pick'],
                $segment['drop'],
                (string) ($ticket['subtrip_stoppage'] ?? '')
            )) {
                continue;
            }
            foreach (BookingInventorySupport::flattenSeatNumbers([$ticket], 'seatnumber') as $seat) {
                $seats[] = $seat;
            }
        }

        return array_values(array_unique($seats));
    }

    /**
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return list<string>
     */
    public function collectTempHoldSeatNumbers(
        int $tripId,
        int $subtripId,
        array $stops,
        $requestedPick,
        $requestedDrop,
        array $departureDates,
        ?string $ignoreTempToken = null,
        ?string $requestedSubtripStoppage = null
    ): array {
        if ($tripId <= 0 || $stops === []) {
            return [];
        }

        $builder = $this->db->table('temporarybooks');
        $builder->select('temporarybooks.seat_names, temporarybooks.journey_date, subtrips.pick_location_id, subtrips.drop_location_id, subtrips.stoppage AS subtrip_stoppage')
            ->join('subtrips', 'temporarybooks.subtrip_id = subtrips.id', 'LEFT')
            ->join('trips', 'subtrips.trip_id = trips.id', 'LEFT');
        BookingInventorySupport::applyTemporaryBookJourneyDateFilter($builder, $departureDates);
        TemporaryBookHoldService::applyActiveHoldExpiryFilter($builder);
        $builder->where('trips.status', 1)
            ->where('subtrips.trip_id', $tripId);

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

        $seats = [];
        $busStart = $departureDates['bus_start'];
        foreach ($builder->get()->getResultArray() as $row) {
            if (! BookingInventorySupport::holdBelongsToBusRun((string) ($row['journey_date'] ?? ''), $busStart)) {
                continue;
            }

            $segment = SegmentSeatAvailability::resolveBookingSegmentFromRow($stops, [
                'pick_location_id' => $row['pick_location_id'] ?? '',
                'drop_location_id' => $row['drop_location_id'] ?? '',
                'subtrip_pick_id' => $row['pick_location_id'] ?? '',
                'subtrip_drop_id' => $row['drop_location_id'] ?? '',
            ]);
            if ($segment === null) {
                continue;
            }
            if (! SegmentSeatAvailability::segmentsOverlapWithPath(
                $stops,
                $requestedPick,
                $requestedDrop,
                $requestedSubtripStoppage,
                $segment['pick'],
                $segment['drop'],
                (string) ($row['subtrip_stoppage'] ?? '')
            )) {
                continue;
            }
            foreach (BookingInventorySupport::flattenSeatNumbers([$row], 'seat_names') as $seat) {
                $seats[] = $seat;
            }
        }

        return array_values(array_unique($seats));
    }

    /**
     * Admin hold_seats for this subtrip + journey dates + Egypt time window.
     *
     * Not segment-overlap logic (that applies to tickets/temporarybooks). Admin selects
     * subtrip IDs in hold_seats.subtrip_id and seat numbers explicitly.
     *
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     * @return list<string>
     */
    public function collectAdminBlockedSeatNumbers(
        int $tripId,
        int $subtripId,
        array $stops,
        $requestedPick,
        $requestedDrop,
        array $departureDates
    ): array {
        unset($stops, $requestedPick, $requestedDrop);

        if ($tripId <= 0 || $subtripId <= 0) {
            return [];
        }

        $seats = [];
        foreach (HoldSeatSupport::fetchActiveHoldRowsForTripSubtrip($tripId, $subtripId, $departureDates['all']) as $row) {
            foreach (HoldSeatSupport::explodeSeatNumbers($row['seat_number'] ?? '') as $seat) {
                $seats[] = $seat;
            }
        }

        return array_values(array_unique($seats));
    }

    /**
     * @param array<string,mixed> $holdRow
     * @param list<string> $stops
     */
    public function adminHoldRowOverlapsSegment(
        array $holdRow,
        array $stops,
        $requestedPick,
        $requestedDrop,
        int $requestSubtripId
    ): bool {
        $subtripIds = array_values(array_filter(array_map('trim', explode(',', (string) ($holdRow['subtrip_id'] ?? '')))));
        if ($subtripIds === []) {
            return false;
        }

        if (! in_array((string) $requestSubtripId, $subtripIds, true)) {
            return false;
        }

        $requestStoppage = '';
        $reqSub = $this->subtripModel->find($requestSubtripId);
        if ($reqSub) {
            $requestStoppage = trim((string) ($reqSub->stoppage ?? ''));
        }

        foreach ($subtripIds as $sid) {
            $sub = $this->subtripModel->find((int) $sid);
            if (! $sub) {
                continue;
            }
            if (SegmentSeatAvailability::segmentsOverlapWithPath(
                $stops,
                $requestedPick,
                $requestedDrop,
                $requestStoppage,
                $sub->pick_location_id,
                $sub->drop_location_id,
                trim((string) ($sub->stoppage ?? ''))
            )) {
                return true;
            }
        }

        return false;
    }

    protected function cleanExpiredTemporaryBooks(?string $preserveToken = null): void
    {
        unset($preserveToken);
        (new TemporaryBookHoldService())->cleanupExpiredHolds($this->db);
    }

}
