<?php

require_once __DIR__ . '/SegmentSeatAvailabilityBootstrap.php';

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

final class TripJourneyDateHelperStorageTest extends TestCase
{
    public function testDisplayReturnsStoredDateWhenTripUnknown(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            '2026-07-20',
            $helper->getPassengerBoardingDateForDisplay(0, 0, '2026-07-20', 0)
        );
    }

    public function testJourneyListSearchMatchesBusStartOnly(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertTrue($helper->rowMatchesJourneyListSearchDate('2026-07-20', '2026-07-21', '2026-07-20'));
        $this->assertFalse($helper->rowMatchesJourneyListSearchDate('2026-07-20', '2026-07-21', '2026-07-21'));
    }

    public function testManifestSearchPrefetchesAdjacentDays(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            ['2026-08-02', '2026-08-03'],
            $helper->getJourneyListPrefetchDates('2026-08-02')
        );
    }

    public function testResolveManifestFromJourneyListRowWithoutTripContextReturnsStored(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            '2026-07-21',
            $helper->resolveManifestDateFromJourneyListRow(0, 0, '2026-07-21', 0)
        );
    }

    public function testTicketSearchIncludesAdjacentDaysForLegacyRows(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            ['2026-07-20', '2026-07-19', '2026-07-21'],
            $helper->getTicketSearchMatchDates('2026-07-20')
        );
    }

    public function testManifestSearchWithoutTripContextUsesStoredListDateOnly(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            0,
            0,
            '2026-07-20',
            0,
            '2026-07-20',
            '2026-07-21 00:00:00'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            0,
            0,
            '2026-07-20',
            0,
            '2026-07-21',
            '2026-07-21 00:00:00'
        ));
    }

    /** Ticket boarding day must not pull a row onto a different manifest search date. */
    public function testManifestSearchIgnoresTicketJourneyData(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            0,
            0,
            '2026-08-02',
            0,
            '2026-08-02',
            '2026-08-03'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            0,
            0,
            '2026-08-02',
            0,
            '2026-08-03',
            '2026-08-03'
        ));
    }

    public function testTripSearchDateIsLiteralBusRunDay(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            '2026-08-03',
            $helper->resolveBusRunDateForTripSearch('2026-08-03')
        );
    }

    public function testTicketStorageUsesBusStartWhenTripUnknown(): void
    {
        $helper = new TripJourneyDateHelper();

        $this->assertSame(
            '2026-08-02',
            $helper->getTicketJourneyDateForStorage(0, 0, '2026-08-02', 0)
        );
    }

    /** journeylists bus run day wins over tickets.journeydata when both agree on manifest date. */
    public function testManifestSearchTrustsStoredBusRunDay(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            75,
            238,
            '2026-06-18',
            8183,
            '2026-06-18',
            '2026-06-18'
        ));
    }

    /** Ticket boarding day must not duplicate the same run onto the next manifest search date. */
    public function testOvernightRunAppearsOnOneManifestDateOnly(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            75,
            238,
            '2026-06-15',
            8182,
            '2026-06-15',
            '2026-06-16'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            75,
            238,
            '2026-06-15',
            8182,
            '2026-06-16',
            '2026-06-16'
        ));
    }

    public function testBusRunInputIsNotTreatedAsBoardingDay(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-06-15',
            $helper->getManifestDateForJourneylist(75, 238, '2026-06-15', 8182)
        );
        $this->assertSame(
            '2026-06-15',
            $helper->boardingDayToBusRunDate(75, 238, '2026-06-16', 8182)
        );
        $this->assertSame(
            '2026-06-16',
            $helper->getTicketJourneyDateForStorage(75, 238, '2026-06-15', 8182)
        );

        $this->assertSame(
            '2026-06-15',
            $helper->boardingDayToBusRunDate(75, 238, '2026-06-16', 8182)
        );

        $this->assertSame(
            '2026-06-15',
            $helper->resolveBusRunDayFromTicketJourneyData(75, 238, '2026-06-16', 8182)
        );
    }

    /** Legacy bus-run storage: midnight pickup shows boarding day (TBHUXO0JRK / TB7IBTD8LX). */
    public function testDisplayShowsBoardingDayWhenJourneyDataStoredBusRunForMidnightPickup(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-08-11',
            $helper->getPassengerBoardingDateForDisplay(117, 400, '2026-08-10', 8581, '2026-08-10')
        );
    }

    /** Canonical boarding in tickets.journeydata must not bump to next day (TBOB9N5YFM). */
    public function testDisplayKeepsCanonicalBoardingDayForThreeAmPickup(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-08-11',
            $helper->getPassengerBoardingDateForDisplay(117, 401, '2026-08-11', 8612)
        );
    }

    /** Legacy service day with manifest also stored as boarding duplicate still shows boarding day. */
    public function testDisplayUsesManifestToPreferStoredBoardingWhenJlWrong(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-08-11',
            $helper->getPassengerBoardingDateForDisplay(117, 401, '2026-08-11', 8612, '2026-08-10')
        );
    }

    /** Correct manifest shows boarding day (TBPS1V9KXD after jl fix). */
    public function testDisplayKeepsBoardingWhenManifestIsServiceDay(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-08-21',
            $helper->getPassengerBoardingDateForDisplay(117, 401, '2026-08-21', 8612, '2026-08-20')
        );
    }

    /** TBB7P23HFY: ticket boarding 9 Sep, manifest/jl 8 Sep. */
    public function testDisplayShowsBoardingWhenTicketAheadOfManifest(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-09-09',
            $helper->getPassengerBoardingDateForDisplay(117, 400, '2026-09-09', 8581, '2026-09-08')
        );
    }

    /** Canonical boarding day in journeydata is shown as-is (same-day / evening pickup). */
    public function testDisplayKeepsBoardingDayWhenJourneyDataAlreadyBoarding(): void
    {
        $helper = $this->trip75CairoElTorPickOffsetMock();

        $this->assertSame(
            '2026-08-16',
            $helper->getPassengerBoardingDateForDisplay(75, 238, '2026-08-16', 8178)
        );
    }

    public function testAdminBookingStorageUsesServiceDayAsBusRunForMidnightPickup(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-06-13',
            $helper->getTicketJourneyDateForStorage(117, 399, '2026-06-12', 8576)
        );
    }

    /** Legacy rows: tickets.journeydata = journeylists.journeydate = service day (TB98WA5UE3). */
    public function testResolveBusRunUsesManifestWhenTicketStoredAsServiceDay(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-06-23',
            $helper->resolveBusRunDayFromTicketJourneyData(117, 400, '2026-06-23', 8581, '2026-06-23')
        );
    }

    /** Canonical rows: tickets.journeydata = boarding day, manifest = service day. */
    public function testResolveBusRunUsesManifestWhenTicketStoredAsBoardingDay(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertSame(
            '2026-06-23',
            $helper->resolveBusRunDayFromTicketJourneyData(117, 400, '2026-06-24', 8581, '2026-06-23')
        );
    }

    /** TB30JAE7YF vs TBJC5SKYOZ: 11:30 PM vs 01:15 AM on search 2026-08-16 are different bus runs. */
    public function testAug16CairoElTorPickStand8178And8184UseDifferentBusRuns(): void
    {
        $helper = $this->trip75CairoElTorPickOffsetMock();

        $this->assertSame(
            '2026-08-16',
            $helper->boardingDayToBusRunDate(75, 238, '2026-08-16', 8178)
        );
        $this->assertSame(
            '2026-08-15',
            $helper->boardingDayToBusRunDate(75, 238, '2026-08-16', 8184)
        );

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            75,
            238,
            '2026-08-16',
            8178,
            '2026-08-16',
            '2026-08-16'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            75,
            238,
            '2026-08-15',
            8184,
            '2026-08-16',
            '2026-08-16'
        ));
    }

    /** Trip 117 AA→GG: Nov 1 manifest includes Nov 2 BB boarding, never Nov 3. */
    public function testNov1ManifestIncludesNov2ExcludesNov3ForMidnightBbTicket(): void
    {
        $helper = $this->trip117OvernightManifestMock();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            117,
            401,
            '2026-11-01',
            8612,
            '2026-11-01',
            '2026-11-02 00:00:00'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            117,
            401,
            '2026-11-01',
            8612,
            '2026-11-01',
            '2026-11-03 00:00:00'
        ));
        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            117,
            396,
            '2026-11-01',
            8606,
            '2026-11-01',
            '2026-11-01 00:00:00'
        ));
        // Safari.pdf row 2 — TBHVJS8D6N: jl 01 Nov, ticket 02 Nov, BB 03:00
        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            117,
            401,
            '2026-11-01',
            8612,
            '2026-11-01',
            '2026-11-02 00:00:00'
        ));
        // Safari.pdf row 3 — TB3QLUWJAI: jl 01 Nov, ticket 02 Nov, 01:30 AM
        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            117,
            396,
            '2026-11-01',
            8713,
            '2026-11-01',
            '2026-11-02 00:00:00'
        ));
        // Safari.pdf row 4 — TBAHZW2QFC: jl 02 Nov, ticket 03 Nov (must not appear on 01 Nov manifest)
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            117,
            401,
            '2026-11-02',
            8612,
            '2026-11-01',
            '2026-11-03 00:00:00'
        ));
    }

    /** Trip 61: Jun 30 bus run = 30 Jun + 1 Jul boarding only; never 2 Jul on that manifest. */
    public function testManifestSearchIncludesJul1ExcludesJul2OnJun30Run(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            61,
            166,
            '2026-07-01',
            10262,
            '2026-06-30',
            '2026-07-01 00:00:00'
        ));
        $this->assertFalse($helper->rowMatchesJourneyListManifestSearch(
            61,
            166,
            '2026-07-02',
            10262,
            '2026-06-30',
            '2026-07-02 00:00:00'
        ));
        $this->assertSame(
            '2026-07-01',
            $helper->getMaxBoardingDateForManifest(61, 166, '2026-06-30')
        );
    }

    /** Canonical bus run jl matches search; legacy boarding on previous run does not bleed via stored===search. */
    public function testManifestSearchUsesBoardingDayNotBlindStoredMatch(): void
    {
        $helper = $this->midnightTripHelperMock();

        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            61,
            166,
            '2026-06-30',
            10262,
            '2026-06-29',
            '2026-06-30 00:00:00'
        ));
        $this->assertTrue($helper->rowMatchesJourneyListManifestSearch(
            61,
            166,
            '2026-06-30',
            10262,
            '2026-06-30',
            '2026-06-30 00:00:00'
        ));
    }

    /** @return TripJourneyDateHelper */
    private function trip117OvernightManifestMock()
    {
        return new class extends TripJourneyDateHelper {
            protected function getPickupDayOffset(object $mainTrip, int $mainTripId, int $subtripId, int $pickStandId): ?int
            {
                if (in_array($pickStandId, [8612, 8713], true)) {
                    return 1;
                }

                return 0;
            }

            public function getBoardingDateFromBusStartDate(int $mainTripId, int $subtripId, string $busStartDate, int $pickStandId): string
            {
                if (in_array($pickStandId, [8612, 8713], true)) {
                    return EgyptTime::toDateString($busStartDate . ' +1 day');
                }

                return EgyptTime::toDateString($busStartDate);
            }

            public function getMaxBoardingDateForManifest(int $mainTripId, int $subtripId, string $manifestSearchDate): string
            {
                return EgyptTime::toDateString($manifestSearchDate . ' +1 day');
            }

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

    /** @return TripJourneyDateHelper */
    private function midnightTripHelperMock()
    {
        return new class extends TripJourneyDateHelper {
            protected function getPickupDayOffset(object $mainTrip, int $mainTripId, int $subtripId, int $pickStandId): ?int
            {
                return 1;
            }

            public function getBoardingDateFromBusStartDate(int $mainTripId, int $subtripId, string $busStartDate, int $pickStandId): string
            {
                return EgyptTime::toDateString($busStartDate . ' +1 day');
            }

            public function getMaxBoardingDateForManifest(int $mainTripId, int $subtripId, string $manifestSearchDate): string
            {
                return EgyptTime::toDateString($manifestSearchDate . ' +1 day');
            }

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

    /** @return TripJourneyDateHelper */
    private function trip75CairoElTorPickOffsetMock()
    {
        return new class extends TripJourneyDateHelper {
            protected function getPickupDayOffset(object $mainTrip, int $mainTripId, int $subtripId, int $pickStandId): ?int
            {
                if ($pickStandId === 8184) {
                    return 1;
                }

                if ($pickStandId === 8178) {
                    return 0;
                }

                return null;
            }

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

}
