<?php

namespace App\Libraries;

use CodeIgniter\Database\BaseConnection;
use Modules\Schedule\Models\ScheduleModel;
use Modules\Trip\Models\SubtripModel;
use Modules\Trip\Models\TripModel;

/**
 * Time-ordered pick/drop sequence for a main trip (physical bus run).
 *
 * Seat overlap must use stand timing — city-level pick/drop on tickets often spans
 * whole subtrips (e.g. Cairo→Sharm) while the passenger only rides 01:15–07:30.
 */
class SegmentRouteTimeline
{
    /** @var array<int, array<int, array{id: int, stand_id: int, time: string, type: int, subtrip_id: int}>> */
    private static array $cache = [];

    /**
     * @return list<int> pickdrop ids in travel order
     */
    public static function pickdropIdsInTravelOrder(int $mainTripId, ?BaseConnection $db = null): array
    {
        $timeline = self::buildTimeline($mainTripId, $db);

        return array_column($timeline, 'id');
    }

    /**
     * @return array<int, int> pickdrop id => travel-order index
     */
    public static function pickdropIndexMap(int $mainTripId, ?BaseConnection $db = null): array
    {
        $map = [];
        foreach (self::buildTimeline($mainTripId, $db) as $idx => $row) {
            $map[(int) $row['id']] = $idx;
        }

        return $map;
    }

    /**
     * @return array{pick: int, drop: int}|null travel-order indices
     */
    public static function resolveTravelIndices(
        int $mainTripId,
        int $pickStandId,
        int $dropStandId,
        ?BaseConnection $db = null
    ): ?array {
        if ($pickStandId <= 0 || $dropStandId <= 0 || $pickStandId === $dropStandId) {
            return null;
        }

        $map = self::pickdropIndexMap($mainTripId, $db);
        $pickPdId = self::resolvePickdropId($mainTripId, $pickStandId, 1, $db);
        $dropPdId = self::resolvePickdropId($mainTripId, $dropStandId, 0, $db);

        if ($pickPdId === null || $dropPdId === null) {
            return null;
        }

        if (! isset($map[$pickPdId], $map[$dropPdId])) {
            return null;
        }

        $pickIdx = $map[$pickPdId];
        $dropIdx = $map[$dropPdId];

        if ($pickIdx === $dropIdx) {
            return null;
        }

        return [
            'pick' => min($pickIdx, $dropIdx),
            'drop' => max($pickIdx, $dropIdx),
        ];
    }

    public static function segmentsOverlapByTravelOrder(
        int $mainTripId,
        int $reqPickStandId,
        int $reqDropStandId,
        int $exPickStandId,
        int $exDropStandId,
        ?BaseConnection $db = null
    ): bool {
        $req = self::resolveTravelIndices($mainTripId, $reqPickStandId, $reqDropStandId, $db);
        $ex = self::resolveTravelIndices($mainTripId, $exPickStandId, $exDropStandId, $db);

        if ($req === null || $ex === null) {
            return false;
        }

        return $ex['pick'] < $req['drop'] && $ex['drop'] > $req['pick'];
    }

    /**
     * Map booked stands to location endpoints on the main stoppage list (fallback / display).
     *
     * @param list<string> $stops
     * @return array{pick: string, drop: string}|null
     */
    public static function resolveLocationSegmentFromStands(
        array $stops,
        int $mainTripId,
        int $pickStandId,
        int $dropStandId,
        ?BaseConnection $db = null
    ): ?array {
        $indices = self::resolveTravelIndices($mainTripId, $pickStandId, $dropStandId, $db);
        if ($indices === null || $stops === []) {
            return null;
        }

        $timeline = self::buildTimeline($mainTripId, $db);
        $pickRow = $timeline[$indices['pick']] ?? null;
        $dropRow = $timeline[$indices['drop']] ?? null;
        if ($pickRow === null || $dropRow === null) {
            return null;
        }

        $pickLoc = self::inferLocationIdForStandLeg($mainTripId, (int) $pickRow['subtrip_id'], (int) $pickRow['stand_id'], $stops, $db);
        $dropLoc = self::inferLocationIdForStandLeg($mainTripId, (int) $dropRow['subtrip_id'], (int) $dropRow['stand_id'], $stops, $db);

        if ($pickLoc === null || $dropLoc === null || $pickLoc === $dropLoc) {
            return null;
        }

        return ['pick' => $pickLoc, 'drop' => $dropLoc];
    }

    /**
     * @return list<array{id: int, stand_id: int, time: string, type: int, subtrip_id: int, sort_key: int}>
     */
    public static function buildTimeline(int $mainTripId, ?BaseConnection $db = null): array
    {
        if ($mainTripId <= 0) {
            return [];
        }

        if (isset(self::$cache[$mainTripId])) {
            return self::$cache[$mainTripId];
        }

        $db = $db ?? \Config\Database::connect();
        $rows = self::loadPickdropsForMainTrip($mainTripId, $db);
        if ($rows === []) {
            self::$cache[$mainTripId] = [];

            return [];
        }

        $mainTrip = (new TripModel())->find($mainTripId);
        $schedule = $mainTrip ? (new ScheduleModel())->find($mainTrip->schedule_id ?? 0) : null;
        $startTime = self::normalizeTime($schedule->start_time ?? '00:00');

        $lastTime = $startTime;
        $dayOffset = 0;
        $enriched = [];

        foreach ($rows as $row) {
            $itemTime = self::normalizeTime($row['time'] ?? '00:00');
            if (strtotime('1970-01-01 ' . $itemTime) < strtotime('1970-01-01 ' . self::normalizeTime($lastTime))) {
                $dayOffset++;
            }
            $lastTime = $itemTime;
            $sortKey = $dayOffset * 86400 + (int) strtotime('1970-01-01 ' . $itemTime);
            $enriched[] = array_merge($row, ['sort_key' => $sortKey]);
        }

        usort($enriched, static function ($a, $b) {
            return $a['sort_key'] <=> $b['sort_key'];
        });

        self::$cache[$mainTripId] = $enriched;

        return $enriched;
    }

    public static function clearCache(): void
    {
        self::$cache = [];
    }

    /**
     * @return list<array{id: int, stand_id: int, time: string, type: int, subtrip_id: int}>
     */
    private static function loadPickdropsForMainTrip(int $mainTripId, BaseConnection $db): array
    {
        $subtripIds = self::subtripIdsForMainTrip($mainTripId);
        if ($subtripIds === []) {
            return [];
        }

        $builder = $db->table('pickdrops')
            ->select('pickdrops.id, pickdrops.stand_id, pickdrops.time, pickdrops.type, pickdrops.trip_id AS subtrip_id')
            ->whereIn('pickdrops.trip_id', $subtripIds)
            ->where('pickdrops.deleted_at', null)
            ->orderBy('pickdrops.id', 'ASC');

        $out = [];
        foreach ($builder->get()->getResultArray() as $row) {
            $out[] = [
                'id' => (int) $row['id'],
                'stand_id' => (int) $row['stand_id'],
                'time' => (string) ($row['time'] ?? ''),
                'type' => (int) ($row['type'] ?? 0),
                'subtrip_id' => (int) $row['subtrip_id'],
            ];
        }

        return $out;
    }

    /**
     * @return list<int>
     */
    private static function subtripIdsForMainTrip(int $mainTripId): array
    {
        $ids = [];
        $subs = (new SubtripModel())->where('trip_id', $mainTripId)->findAll();
        foreach ($subs as $sub) {
            $ids[] = (int) $sub->id;
        }

        return $ids;
    }

    private static function resolvePickdropId(int $mainTripId, int $standOrPickdropId, int $type, ?BaseConnection $db): ?int
    {
        $db = $db ?? \Config\Database::connect();

        $byId = $db->table('pickdrops')
            ->select('id, trip_id, type')
            ->where('id', $standOrPickdropId)
            ->where('deleted_at', null)
            ->get()
            ->getRowArray();

        if ($byId) {
            $subtripIds = self::subtripIdsForMainTrip($mainTripId);
            if (in_array((int) $byId['trip_id'], $subtripIds, true)) {
                return (int) $byId['id'];
            }
        }

        $subtripIds = self::subtripIdsForMainTrip($mainTripId);
        if ($subtripIds === []) {
            return null;
        }

        $byStand = $db->table('pickdrops')
            ->select('id')
            ->whereIn('trip_id', $subtripIds)
            ->where('stand_id', $standOrPickdropId)
            ->where('type', $type)
            ->where('deleted_at', null)
            ->orderBy('id', 'ASC')
            ->get()
            ->getRowArray();

        return $byStand ? (int) $byStand['id'] : null;
    }

    /**
     * @param list<string> $stops
     */
    private static function inferLocationIdForStandLeg(
        int $mainTripId,
        int $subtripId,
        int $standId,
        array $stops,
        ?BaseConnection $db
    ): ?string {
        $sub = (new SubtripModel())->find($subtripId);
        if (! $sub) {
            return null;
        }

        $subStops = SegmentSeatAvailability::parseStoppage($sub->stoppage ?? '');
        if ($subStops === []) {
            $subStops = $stops;
        }

        $pickdrops = $db->table('pickdrops')
            ->select('id, stand_id, type')
            ->where('trip_id', $subtripId)
            ->where('deleted_at', null)
            ->orderBy('id', 'ASC')
            ->get()
            ->getResultArray();

        $pickIdx = null;
        $dropIdx = null;
        foreach ($pickdrops as $i => $pd) {
            if ((int) $pd['stand_id'] === $standId) {
                if ((int) $pd['type'] === 1) {
                    $pickIdx = $i;
                } else {
                    $dropIdx = $i;
                }
            }
        }

        $pdCount = count($pickdrops);
        if ($pickIdx !== null && $subStops !== []) {
            $ratio = $pdCount > 1 ? $pickIdx / ($pdCount - 1) : 0;
            $locIdx = (int) round($ratio * (count($subStops) - 1));

            return (string) $subStops[$locIdx];
        }
        if ($dropIdx !== null && $subStops !== []) {
            $ratio = $pdCount > 1 ? $dropIdx / ($pdCount - 1) : 1;
            $locIdx = (int) round($ratio * (count($subStops) - 1));

            return (string) $subStops[$locIdx];
        }

        return $subStops !== [] ? (string) $subStops[0] : null;
    }

    private static function normalizeTime(string $time): string
    {
        $time = trim($time);
        if ($time === '') {
            return '00:00';
        }
        $timestamp = strtotime('1970-01-01 ' . $time);

        return $timestamp !== false ? date('H:i', $timestamp) : '00:00';
    }
}
