<?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 trips: bus departs day N (Cairo), passenger boards day N+1 (e.g. Ras Sudr).
        // Inventory must match tickets.journeydata on bus-start date AND boarding calendar date.
        if ($tripId > 0 && $subtripId > 0 && $boardingDate !== null && $boardingDate !== '') {
            if ($pickStandId <= 0) {
                $pickStandId = self::resolveDefaultPickStandId($tripId, $subtripId);
            }

            if ($pickStandId > 0) {
                $helper = new TripJourneyDateHelper();
                $busStart = $helper->getBusStartDateForJourneylist($tripId, $subtripId, $boarding, $pickStandId);
            }
            // If no pick stand: keep $busStart from $busStartDate (caller may have computed it already).
        }

        return [
            'bus_start' => $busStart,
            'boarding' => $boarding,
            'all' => self::expandDepartureDateKeys($busStart, $boarding),
        ];
    }

    /**
     * Calendar dates used to load tickets/temp books for one physical bus departure.
     * Midnight / multi-day trips can store different bus-start values per subtrip; include
     * a small window around bus_start and boarding so inventory never misses siblings.
     *
     * @return list<string>
     */
    public static function expandDepartureDateKeys(string $busStart, string $boarding): array
    {
        $busStart = date('Y-m-d', strtotime($busStart));
        $boarding = date('Y-m-d', strtotime($boarding));

        $dates = [$busStart, $boarding];
        $spanDays = (int) abs((strtotime($boarding) - strtotime($busStart)) / 86400);
        $pad = max(2, min(4, $spanDays + 1));

        foreach ([$busStart, $boarding] as $anchor) {
            for ($i = 1; $i <= $pad; $i++) {
                $dates[] = date('Y-m-d', strtotime($anchor . " -{$i} day"));
                $dates[] = date('Y-m-d', strtotime($anchor . " +{$i} day"));
            }
        }

        return array_values(array_unique($dates));
    }

    /**
     * @param array{bus_start?: string, boarding?: string, all?: list<string>}|list<string> $departureDates
     * @return array{bus_start: string, boarding: string, all: list<string>}
     */
    public static function normalizeDepartureDates($departureDates): array
    {
        if (isset($departureDates['all']) || isset($departureDates['bus_start'])) {
            $busStart = isset($departureDates['bus_start'])
                ? date('Y-m-d', strtotime((string) $departureDates['bus_start']))
                : date('Y-m-d', strtotime((string) ($departureDates['all'][0] ?? 'today')));
            $boarding = isset($departureDates['boarding'])
                ? date('Y-m-d', strtotime((string) $departureDates['boarding']))
                : $busStart;
            $all = $departureDates['all'] ?? self::expandDepartureDateKeys($busStart, $boarding);

            return [
                'bus_start' => $busStart,
                'boarding' => $boarding,
                'all' => array_values(array_unique(array_filter(array_map(static function ($d) {
                    return date('Y-m-d', strtotime((string) $d));
                }, $all)))),
            ];
        }

        $list = array_values(array_unique(array_filter(array_map(static function ($d) {
            return date('Y-m-d', strtotime((string) $d));
        }, (array) $departureDates))));

        $busStart = $list[0] ?? date('Y-m-d');
        $boarding = $list[1] ?? $busStart;

        return [
            'bus_start' => $busStart,
            'boarding' => $boarding,
            'all' => $list !== [] ? $list : self::expandDepartureDateKeys($busStart, $boarding),
        ];
    }

    /**
     * First pickup pickdrops.id for a subtrip (pickdrops.trip_id = subtrips.id).
     */
    public static function resolveDefaultPickStandId(int $tripId, int $subtripId): int
    {
        if ($subtripId <= 0) {
            return 0;
        }

        $db = \Config\Database::connect();

        $row = $db->table('pickdrops')
            ->select('id')
            ->where('trip_id', $subtripId)
            ->where('type', 1)
            ->orderBy('id', 'ASC')
            ->get()
            ->getRow();

        if ($row) {
            return (int) $row->id;
        }

        $mainSub = $db->table('subtrips')
            ->select('id')
            ->where('trip_id', $tripId)
            ->where('type', 'main')
            ->get()
            ->getRow();

        if ($mainSub) {
            $row = $db->table('pickdrops')
                ->select('id')
                ->where('trip_id', (int) $mainSub->id)
                ->where('type', 1)
                ->orderBy('id', 'ASC')
                ->get()
                ->getRow();

            if ($row) {
                return (int) $row->id;
            }
        }

        return 0;
    }

    /**
     * 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);

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

    /**
     * Confirmed tickets for one physical bus departure.
     * Primary key: journeylists.journeydate = bus_start; also match boarding dates on tickets.journeydata.
     *
     * @param array{bus_start?: string, boarding?: string, all?: list<string>}|list<string> $departureDates
     */
    public static function applyTicketJourneyDateFilter(BaseBuilder $builder, int $tripId, $departureDates): void
    {
        $dates = self::normalizeDepartureDates($departureDates);
        $matchDates = $dates['all'];
        $busStart = $dates['bus_start'];

        if ($matchDates === []) {
            return;
        }

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

            $jl->groupStart();
            $jl->where('DATE(journeylists.journeydate)', $busStart);
            foreach ($matchDates as $d) {
                if ($d !== $busStart) {
                    $jl->orWhere('DATE(journeylists.journeydate)', $d);
                }
            }
            $jl->groupEnd();

            $bookingIds = array_values(array_unique(array_column($jl->get()->getResultArray(), 'booking_id')));
        }

        $builder->groupStart();

        if ($bookingIds !== []) {
            $builder->whereIn('tickets.booking_id', $bookingIds);
            $builder->orGroupStart();
        }

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

        if ($bookingIds !== []) {
            $builder->groupEnd();
        }

        $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>
     */
    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));
    }
}
