<?php

require_once __DIR__ . '/SegmentSeatAvailabilityBootstrap.php';

use App\Libraries\OvernightTripRunSupport;
use App\Libraries\TripJourneyDateHelper;
use PHPUnit\Framework\TestCase;

/**
 * Overnight run: A (10PM/11PM/1AM) → B (4AM/4:30/5AM) → F on one physical bus.
 *
 * @internal
 */
final class OvernightTripRunSupportTest extends TestCase
{
    /** @var array<int, int> pickStandId => day offset from service date */
    private array $pickOffsets = [
        1001 => 0, // A 10 PM — 16 Aug
        1002 => 0, // A 11 PM — 16 Aug
        1003 => 1, // A 1 AM — 17 Aug
        2001 => 1, // B 4 AM — 17 Aug
        2002 => 1, // B 4:30 AM — 17 Aug
        2003 => 1, // B 5 AM — 17 Aug
    ];

    public function testSearch16AugShowsOnlyAPickupsOnSameServiceRun(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $eligible = $run->pickStandsForBoardingSearchDate(75, 238, '2026-08-16');

        $this->assertSame([1001, 1002], $eligible);
        $this->assertFalse($run->subtripVisibleOnBoardingSearchDate(75, 404, '2026-08-16'));
    }

    public function testSearch17AugShowsLateAPickupAndAllBPickups(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $eligibleA = $run->pickStandsForBoardingSearchDate(75, 238, '2026-08-17');
        $eligibleB = $run->pickStandsForBoardingSearchDate(75, 404, '2026-08-17');

        $this->assertSame([1003], $eligibleA);
        $this->assertSame([2001, 2002, 2003], $eligibleB);
    }

    public function testSameServiceRunDifferentBoardingDates(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $ctx10pm = $run->resolveBoardingSearch(75, 238, '2026-08-16', 1001);
        $ctx4am  = $run->resolveBoardingSearch(75, 404, '2026-08-17', 2001);

        $this->assertSame('2026-08-16', $ctx10pm['service_date']);
        $this->assertSame('2026-08-16', $ctx10pm['boarding_date']);
        $this->assertSame('2026-08-16', $ctx4am['service_date']);
        $this->assertSame('2026-08-17', $ctx4am['boarding_date']);
        $this->assertContains('2026-08-16', $ctx4am['inventory_dates']);
        $this->assertContains('2026-08-17', $ctx4am['inventory_dates']);
    }

    public function testManifestServiceDateMatchesForEveningAndAfterMidnightBoarding(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $evening = $run->resolveBoardingSearch(75, 238, '2026-08-16', 1002);
        $late    = $run->resolveBoardingSearch(75, 238, '2026-08-17', 1003);

        $this->assertSame($evening['service_date'], $late['service_date']);
        $this->assertSame('2026-08-16', $evening['service_date']);
    }

    public function testRequiresExplicitPickStandForOvernightSubtrip(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $this->assertTrue($run->requiresExplicitPickStand(75, 238));
        $this->assertSame(
            'pickstand is required for overnight trips before checking seat availability.',
            $run->validatePickStandForSeatInventory(75, 238, 0)
        );
        $this->assertNull($run->validatePickStandForSeatInventory(75, 238, 1001));
    }

    public function testPassengerListGroupedByBoardingDateNotServiceDate(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $this->assertTrue($run->passengerListMatchesSearchDate(
            75,
            238,
            1001,
            '2026-08-16',
            '2026-08-16',
            '2026-08-16'
        ));
        $this->assertFalse($run->passengerListMatchesSearchDate(
            75,
            404,
            2001,
            '2026-08-16',
            '2026-08-17',
            '2026-08-16'
        ));
        $this->assertTrue($run->passengerListMatchesSearchDate(
            75,
            404,
            2001,
            '2026-08-17',
            '2026-08-17',
            '2026-08-16'
        ));
    }

    public function testDriverManifestStillUsesServiceDate(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $this->assertTrue($run->driverManifestMatchesSearchDate(
            75,
            404,
            '2026-08-16',
            2001,
            '2026-08-16',
            '2026-08-17'
        ));
        $this->assertFalse($run->driverManifestMatchesSearchDate(
            75,
            404,
            '2026-08-16',
            2001,
            '2026-08-17',
            '2026-08-17'
        ));
    }

    public function testFilterPickdropsHidesWrongBoardingDayPickups(): void
    {
        $helper = $this->helperMock();
        $run    = new OvernightTripRunSupport($helper);

        $rows = [
            (object) ['pickdropid' => 1001, 'type' => 1, 'time' => '10:00PM'],
            (object) ['pickdropid' => 1003, 'type' => 1, 'time' => '01:00AM'],
            (object) ['pickdropid' => 9001, 'type' => 0, 'time' => '08:00AM'],
        ];

        $filtered = $run->filterPickdropsForBoardingSearch(75, 238, '2026-08-16', $rows);
        $ids      = array_map(static fn ($r) => (int) $r->pickdropid, $filtered);

        $this->assertSame([1001, 9001], $ids);
    }

    public function testShortSegmentResellingDisabledByPolicy(): void
    {
        $this->assertFalse(OvernightTripRunSupport::allowsShortSegmentReselling());
    }

    /** @return TripJourneyDateHelper */
    private function helperMock()
    {
        $offsets = $this->pickOffsets;

        return new class($offsets) extends TripJourneyDateHelper {
            /** @var array<int, int> */
            private array $offsets;

            /** @param array<int, int> $offsets */
            public function __construct(array $offsets)
            {
                parent::__construct();
                $this->offsets = $offsets;
            }

            public function listSegmentPickupStandIds(int $mainTripId, int $subtripId): array
            {
                if ($subtripId === 238) {
                    return [1001, 1002, 1003];
                }
                if ($subtripId === 404) {
                    return [2001, 2002, 2003];
                }

                return [];
            }

            protected function segmentHasOnlyAfterMidnightPickups(int $mainTripId, int $subtripId): bool
            {
                return $subtripId === 404;
            }

            public function getBoardingDateFromBusStartDate(int $mainTripId, int $subtripId, string $busStartDate, int $pickStandId): string
            {
                $service = \App\Libraries\EgyptTime::toDateString($busStartDate);
                $map     = [
                    '2026-08-16' => [
                        1001 => '2026-08-16',
                        1002 => '2026-08-16',
                        1003 => '2026-08-17',
                        2001 => '2026-08-17',
                        2002 => '2026-08-17',
                        2003 => '2026-08-17',
                    ],
                ];

                if (isset($map[$service][$pickStandId])) {
                    return $map[$service][$pickStandId];
                }

                return '1970-01-01';
            }

            protected function getPickupDayOffset(object $mainTrip, int $mainTripId, int $subtripId, int $pickStandId): ?int
            {
                return $this->offsets[$pickStandId] ?? null;
            }

            protected function getTrip(int $mainTripId): ?object
            {
                return (object) ['schedule_id' => 1];
            }

            protected function getPickdropById(int $pickStandId): ?object
            {
                $times = [
                    1001 => '10:00PM',
                    1002 => '11:00PM',
                    1003 => '01:00AM',
                    2001 => '04:00AM',
                    2002 => '04:30AM',
                    2003 => '05:00AM',
                ];

                if (! isset($times[$pickStandId])) {
                    return null;
                }

                return (object) ['id' => $pickStandId, 'time' => $times[$pickStandId], 'type' => 1];
            }
        };
    }
}
