<?php

namespace App\Libraries;

use CodeIgniter\Database\BaseBuilder;
use CodeIgniter\Database\BaseConnection;
use Modules\Ticket\Models\TicketModel;
use Modules\Trip\Models\TripModel;
use Modules\Trip\Models\SubtripModel;
use App\Libraries\HoldSeatSupport;

/**
 * Trip-level seat inventory: main route stoppage + bus departure dates (incl. midnight trips).
 */
class BookingInventorySupport
{
    public static function resolveTripStoppageForOverlap(int $tripId, int $subtripId = 0): string
    {
        if ($tripId > 0) {
            $mainTrip = (new TripModel())->find($tripId);
            if ($mainTrip && trim((string) ($mainTrip->stoppage ?? '')) !== '') {
                return trim((string) $mainTrip->stoppage);
            }
        }

        if ($subtripId > 0) {
            $sub = (new SubtripModel())->find($subtripId);

            return $sub ? trim((string) ($sub->stoppage ?? '')) : '';
        }

        return '';
    }

    /**
     * Bus start date (inventory key) and passenger boarding calendar date (midnight trips).
     *
     * @return array{bus_start: string, boarding: string, all: list<string>}
     */
    public static function resolveDepartureDates(
        int $tripId,
        int $subtripId,
        string $busStartDate,
        ?string $boardingDate = null,
        int $pickStandId = 0
    ): array {
        $boarding = $boardingDate !== null && $boardingDate !== ''
            ? date('Y-m-d', strtotime($boardingDate))
            : date('Y-m-d', strtotime($busStartDate));

        $busStart = date('Y-m-d', strtotime($busStartDate));

        // Midnight: recompute bus start from boarding date + pickup stand when possible.
        if ($tripId > 0 && $subtripId > 0 && $boardingDate !== null && $boardingDate !== '') {
            $helper = new TripJourneyDateHelper();
            $busStart = $helper->getBusStartDateForJourneylist($tripId, $subtripId, $boarding, $pickStandId);
        }

        return [
            'bus_start' => $busStart,
            'boarding' => $boarding,
            'all' => array_values(array_unique([$busStart, $boarding])),
        ];
    }

    /**
     * All seat numbers on a trip departure (any segment). Use only for reporting — not for
     * segment seat maps; the same seat may be sold again after drop-off on a later leg.
     *
     * @return list<string>
     */
    public static function getAllBookedSeatsOnTripDeparture(
        int $tripId,
        int $subtripId,
        string $busStartDate,
        ?string $boardingDate = null,
        int $pickStandId = 0
    ): array {
        if ($tripId <= 0) {
            return [];
        }

        $dates = self::resolveDepartureDates($tripId, $subtripId, $busStartDate, $boardingDate, $pickStandId);

        $builder = (new TicketModel())->builder();
        $builder->select('tickets.seatnumber, tickets.booking_id')
            ->where('tickets.trip_id', $tripId)
            ->where('tickets.cancel_status', 0)
            ->where('tickets.refund', 0);

        self::applyTicketJourneyDateFilter($builder, $tripId, $dates['all']);

        return self::flattenSeatNumbers($builder->get()->getResultArray(), 'seatnumber');
    }

    /**
     * Confirmed + temp overlap queries: match journeydata on both bus-start and boarding dates.
     */
    public static function applyTicketJourneyDateFilter(BaseBuilder $builder, int $tripId, array $departureDates): void
    {
        $dates = array_values(array_unique(array_filter(array_map(static function ($d) {
            return date('Y-m-d', strtotime((string) $d));
        }, $departureDates))));

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

        // Overnight trips: widen SQL filter; ticketMatchesBusDeparture() enforces bus-start match.
        $expanded = $dates;
        foreach ($dates as $d) {
            $expanded[] = date('Y-m-d', strtotime($d . ' -1 day'));
            $expanded[] = date('Y-m-d', strtotime($d . ' +1 day'));
        }
        $expanded = array_values(array_unique($expanded));

        $builder->groupStart();

        foreach ($expanded as $i => $d) {
            if ($i === 0) {
                $builder->where('DATE(tickets.journeydata)', $d);
            } else {
                $builder->orWhere('DATE(tickets.journeydata)', $d);
            }
        }

        if ($tripId > 0 && count($dates) > 0) {
            $db = \Config\Database::connect();
            $jl = $db->table('journeylists')->select('journeylists.booking_id')
                ->join('tickets tjl', 'tjl.booking_id = journeylists.booking_id', 'inner')
                ->where('tjl.trip_id', $tripId)
                ->where('tjl.cancel_status', 0)
                ->where('tjl.refund', 0);

            $jl->groupStart();
            foreach ($dates as $j => $d) {
                if ($j === 0) {
                    $jl->where('DATE(journeylists.journeydate)', $d);
                } else {
                    $jl->orWhere('DATE(journeylists.journeydate)', $d);
                }
            }
            $jl->groupEnd();

            $bookingIds = array_column($jl->get()->getResultArray(), 'booking_id');
            if (! empty($bookingIds)) {
                $builder->orWhereIn('tickets.booking_id', array_unique($bookingIds));
            }
        }

        $builder->groupEnd();
    }

    public static function applyTemporaryBookJourneyDateFilter(BaseBuilder $builder, array $departureDates): void
    {
        $dates = array_values(array_unique(array_filter(array_map(static function ($d) {
            return date('Y-m-d', strtotime((string) $d));
        }, $departureDates))));

        if (count($dates) === 1) {
            $builder->where('DATE(temporarybooks.journey_date)', $dates[0]);

            return;
        }

        $builder->groupStart();
        foreach ($dates as $i => $d) {
            if ($i === 0) {
                $builder->where('DATE(temporarybooks.journey_date)', $d);
            } else {
                $builder->orWhere('DATE(temporarybooks.journey_date)', $d);
            }
        }
        $builder->groupEnd();
    }

    /**
     * Active admin hold seats for a trip departure (trip-level holds).
     *
     * @return list<string>
     */
    public static function getAdminHeldSeatsOnTripDeparture(
        int $tripId,
        string $busStartDate,
        ?string $boardingDate = null
    ): array {
        if ($tripId <= 0) {
            return [];
        }

        $dates = self::resolveDepartureDates($tripId, 0, $busStartDate, $boardingDate);

        return self::flattenSeatNumbers(
            HoldSeatSupport::fetchActiveHoldRowsForTrip($tripId, $dates['all']),
            'seat_number'
        );
    }

    /**
     * Same-bus seats + admin holds (segment overlap handled separately).
     *
     * @return list<string>
     */
    public static function getTripDepartureBlockedSeats(
        int $tripId,
        int $subtripId,
        string $busStartDate,
        ?string $boardingDate = null,
        int $pickStandId = 0
    ): array {
        return array_values(array_unique(array_merge(
            self::getAllBookedSeatsOnTripDeparture($tripId, $subtripId, $busStartDate, $boardingDate, $pickStandId),
            self::getAdminHeldSeatsOnTripDeparture($tripId, $busStartDate, $boardingDate)
        )));
    }

    public static function lockTripRowForUpdate(BaseConnection $db, int $tripId): void
    {
        if ($tripId > 0) {
            $db->query('SELECT id FROM trips WHERE id = ? FOR UPDATE', [$tripId]);
        }
    }

    /**
     * @param list<array<string,mixed>> $rows
     * @return list<string>
     */
    /**
     * Same physical bus departure: ticket journeydata may store bus-start or boarding date.
     *
     * @param array<string,mixed> $ticketRow
     * @param array{bus_start: string, boarding: string, all: list<string>} $departureDates
     */
    public static function ticketMatchesBusDeparture(int $tripId, array $ticketRow, array $departureDates): bool
    {
        $journey = date('Y-m-d', strtotime((string) ($ticketRow['journeydata'] ?? '')));
        $allowed = array_flip($departureDates['all'] ?? []);

        if (isset($allowed[$journey])) {
            return true;
        }

        $subtripId = (int) ($ticketRow['subtrip_id'] ?? 0);
        $pickStandId = (int) ($ticketRow['pick_stand_id'] ?? 0);
        if ($tripId <= 0 || $subtripId <= 0 || $journey === '1970-01-01') {
            return false;
        }

        $busStart = (new TripJourneyDateHelper())->getBusStartDateForJourneylist(
            $tripId,
            $subtripId,
            $journey,
            $pickStandId
        );

        return isset($allowed[$busStart]);
    }

    public static function flattenSeatNumbers(array $rows, string $seatColumn): array
    {
        $out = [];
        foreach ($rows as $row) {
            foreach (array_filter(array_map('trim', explode(',', (string) ($row[$seatColumn] ?? '')))) as $seat) {
                if ($seat !== '') {
                    $out[] = $seat;
                }
            }
        }

        return array_values(array_unique($out));
    }
}
