<?php

require_once __DIR__ . '/SegmentSeatAvailabilityBootstrap.php';

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

final class BookingInventorySupportDatesTest extends TestCase
{
    public function testRegularTripUsesSingleDateForInventory(): void
    {
        $dates = BookingInventorySupport::buildInventoryMatchDates('2026-07-14', '2026-07-14');

        $this->assertSame(['2026-07-14'], $dates);
    }

    public function testMidnightTripIncludesRunAndBoardingDates(): void
    {
        $dates = BookingInventorySupport::buildInventoryMatchDates('2026-07-14', '2026-07-15');

        $this->assertContains('2026-07-14', $dates);
        $this->assertContains('2026-07-15', $dates);
    }

    public function testExpandInventoryDatesIncludesAllBoardingDaysOnBusRun(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('trip_id', 400)->where('type', 1)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Trip 117 subtrip 400 pickdrops not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::expandInventoryDatesForBusRun(
            117,
            400,
            '2026-06-23',
            ['2026-06-23']
        );

        $this->assertContains('2026-06-23', $dates);
        $this->assertContains('2026-06-24', $dates);
    }

    public function testTicketBelongsToBusRunWithLegacyServiceDayStorageAndManifest(): void
    {
        $this->assertTrue(BookingInventorySupport::ticketBelongsToBusRun(
            117,
            400,
            '2026-06-23',
            8581,
            '2026-06-23',
            '2026-06-23'
        ));
    }

    public function testMidnightTripDoesNotAddExtraLookbackDays(): void
    {
        $dates = BookingInventorySupport::buildInventoryMatchDates('2026-08-02', '2026-08-03');

        $this->assertSame(['2026-08-02', '2026-08-03'], $dates);
        $this->assertNotContains('2026-08-01', $dates);
        $this->assertNotContains('2026-08-04', $dates);
    }

    public function testNormalizeDepartureDatesKeepsProvidedValues(): void
    {
        $normalized = BookingInventorySupport::normalizeDepartureDates([
            'bus_start' => '2026-07-14',
            'boarding' => '2026-07-15',
            'all' => ['2026-07-14', '2026-07-15'],
        ]);

        $this->assertSame('2026-07-14', $normalized['bus_start']);
        $this->assertSame('2026-07-15', $normalized['boarding']);
        $this->assertSame(['2026-07-14', '2026-07-15'], $normalized['all']);
    }

    public function testResolveDepartureDatesDoesNotRecomputeBusStartFromBoarding(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8182)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Trip 75 pickdrop 8182 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveDepartureDates(
            75,
            238,
            '2026-07-14',
            '2026-07-15',
            8182
        );

        $this->assertSame('2026-07-14', $dates['bus_start']);
        $this->assertSame('2026-07-15', $dates['boarding']);
        $this->assertContains('2026-07-14', $dates['all']);
        $this->assertContains('2026-07-15', $dates['all']);
    }

    public function testBusRunForTripSearchDoesNotShiftMidnightSearchDay(): void
    {
        $this->assertSame('2026-08-03', BookingInventorySupport::busRunForTripSearch('2026-08-03'));
    }

    public function testHoldBelongsToBusRunUsesSameCalendarNormalization(): void
    {
        $this->assertTrue(BookingInventorySupport::holdBelongsToBusRun('2026-08-02 00:00:00', '2026-08-02'));
        $this->assertFalse(BookingInventorySupport::holdBelongsToBusRun('2026-08-03', '2026-08-02'));
    }

    public function testBusRunForBookingNormalizesWithoutTripContext(): void
    {
        $this->assertSame(
            '2026-06-15',
            BookingInventorySupport::busRunForBooking(0, 0, '2026-06-15', 0)
        );
    }

    public function testResolveSearchDepartureDatesWithoutTripContextUsesLiteralDate(): void
    {
        $dates = BookingInventorySupport::resolveSearchDepartureDates(0, 0, '2026-08-17');

        $this->assertSame('2026-08-17', $dates['bus_start']);
        $this->assertSame('2026-08-17', $dates['boarding']);
        $this->assertSame(['2026-08-17'], $dates['all']);
    }

    /**
     * Ras Sedr boarding 17-Aug on the 16-Aug overnight run must not block a 17-Aug Cairo evening search.
     */
    public function testTicketBelongsToBusRunRejectsPreviousNightRunWhenJourneydataMatchesSearchDay(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8664)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Trip 75 pickdrop 8664 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $this->assertFalse(BookingInventorySupport::ticketBelongsToBusRun(
            75,
            404,
            '2026-08-17',
            8664,
            '2026-08-17'
        ));

        $this->assertTrue(BookingInventorySupport::ticketBelongsToBusRun(
            75,
            404,
            '2026-08-17',
            8664,
            '2026-08-16'
        ));
    }

    /** Scenario 2: AA origin, search 07 Jul (service intent) + 01:00 stand → jl 07 Jul, ticket 08 Jul. */
    public function testScenario2AaServiceSearchJul7OneAmPickup(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveSearchDepartureDates(117, 400, '2026-07-07', 8581);

        $this->assertSame('2026-07-07', $dates['bus_start']);
        $this->assertSame('2026-07-08', $dates['boarding']);
    }

    /** Scenario 3: AA origin, search 08 Jul (boarding intent) + 01:00 stand → same 07 Jul service run. */
    public function testScenario3AaBoardingSearchJul8OneAmPickup(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveSearchDepartureDates(117, 400, '2026-07-08', 8581);

        $this->assertSame('2026-07-07', $dates['bus_start']);
        $this->assertSame('2026-07-08', $dates['boarding']);
    }

    /** Duplicate prevention: Scenario 2 and 3 must share the same inventory service date. */
    public function testScenario2And3ShareServiceDateForInventory(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $fromServiceSearch = BookingInventorySupport::resolveSearchDepartureDates(117, 400, '2026-07-07', 8581);
        $fromBoardingSearch = BookingInventorySupport::resolveSearchDepartureDates(117, 400, '2026-07-08', 8581);

        $this->assertSame($fromServiceSearch['bus_start'], $fromBoardingSearch['bus_start']);
        $this->assertSame($fromServiceSearch['boarding'], $fromBoardingSearch['boarding']);
    }

    /** Trip 117 BB→GG: boarding search 8 Jul → service run 7 Jul (overnight pickup). */
    public function testSearchDepartureDatesBbBoardingJul8ResolvesToJul7Service(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8612)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8612 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveSearchDepartureDates(117, 401, '2026-07-08', 8612);

        $this->assertSame('2026-07-07', $dates['bus_start']);
        $this->assertSame('2026-07-08', $dates['boarding']);
    }

    /** Trip 117 BB→GG: boarding search 7 Jul is a different (earlier) bus run, not the 7 Jul service. */
    public function testSearchDepartureDatesBbBoardingJul7DoesNotUseJul7Service(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8612)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8612 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveSearchDepartureDates(117, 401, '2026-07-07', 8612);

        $this->assertNotSame('2026-07-07', $dates['bus_start']);
        $this->assertSame('2026-07-07', $dates['boarding']);
    }

    public function testResolveBookingStorageDatesUsesBoardingSearchByDefault(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8612)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8612 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveBookingStorageDates(117, 401, '2026-07-08', 8612);

        $this->assertSame('2026-07-07', $dates['bus_start']);
        $this->assertSame('2026-07-08', $dates['boarding']);
    }

    /** TBRTWZAC5V: app search 15 Jun boarding + 01:00 stand → ticket 15 Jun, service 14 Jun (not 16 Jun). */
    public function testJun15BoardingSearchOneAmPickupStoresFifteenthNotSixteenth(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveBookingStorageDates(117, 400, '2026-06-15', 8581, null, '2026-06-15');

        $this->assertSame('2026-06-14', $dates['bus_start']);
        $this->assertSame('2026-06-15', $dates['boarding']);
        $this->assertNotSame('2026-06-16', $dates['boarding']);
    }

    /** TBOYR1FI84 class: 24 Jun search → journeydata 25 Jun must not match 25 Jun bus run. */
    public function testPriorNightTicketJourneydataDoesNotMatchNextBusRun(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $this->assertFalse(BookingInventorySupport::ticketBelongsToBusRun(
            117,
            400,
            '2026-06-25',
            8581,
            '2026-06-25'
        ));

        $this->assertTrue(BookingInventorySupport::ticketBelongsToBusRun(
            117,
            400,
            '2026-06-25',
            8581,
            '2026-06-24'
        ));
    }

    /** Search 17 Jun (service day) + 01:00 stand → ticket 18 Jun, service 17 Jun (TB8Z0EPD94 class). */
    public function testJun17ServiceSearchOneAmPickupStoresEighteenthNotSeventeenth(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveSearchDepartureDates(117, 400, '2026-06-17', 8581);

        $this->assertSame('2026-06-17', $dates['bus_start']);
        $this->assertSame('2026-06-18', $dates['boarding']);
        $this->assertNotSame('2026-06-17', $dates['boarding']);
    }

    /** TBXSQLCGPH: search 21 Jun + 01:00 → boarding 22 Jun (not 23). Geidea must not resolve twice. */
    public function testJun21SearchOneAmPickupBoardsTwentySecondNotTwentyThird(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $fromSearch = BookingInventorySupport::resolveBookingStorageDates(117, 400, '2026-06-21', 8581);
        $this->assertSame('2026-06-21', $fromSearch['bus_start']);
        $this->assertSame('2026-06-22', $fromSearch['boarding']);

        // Bug: processPayment stored boarding (22) then bookticketGeidea treated it as search → 23.
        $doubleResolved = BookingInventorySupport::resolveBookingStorageDates(117, 400, $fromSearch['boarding'], 8581);
        $this->assertSame('2026-06-23', $doubleResolved['boarding'], 'Documents the double-conversion bug when boarding is passed as search');
        $this->assertNotSame($fromSearch['boarding'], $doubleResolved['boarding']);
    }

    /**
     * Regular same-day trip: search = boarding = bus_start.
     * Storing search vs boarding in paystatusgeidea is identical — Geidea fix is a no-op.
     */
    public function testSameDayEveningPickupGeideaFlowUnchanged(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8578)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8578 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $search = '2026-06-29';
        $once = BookingInventorySupport::resolveBookingStorageDates(117, 400, $search, 8578);

        $this->assertSame($search, $once['bus_start']);
        $this->assertSame($search, $once['boarding']);

        // Fixed Geidea: paystatus stores search → bookticketGeidea resolves once.
        $fixedFlow = BookingInventorySupport::resolveBookingStorageDates(117, 400, $search, 8578);
        $this->assertSame($once['boarding'], $fixedFlow['boarding']);
        $this->assertSame($once['bus_start'], $fixedFlow['bus_start']);

        // Pre-fix Geidea also worked for same-day (stored boarding === search).
        $legacyPaystatusValue = $once['boarding'];
        $this->assertSame($search, $legacyPaystatusValue);
        $legacyFlow = BookingInventorySupport::resolveBookingStorageDates(117, 400, $legacyPaystatusValue, 8578);
        $this->assertSame($once['boarding'], $legacyFlow['boarding']);
        $this->assertSame($once['bus_start'], $legacyFlow['bus_start']);
    }

    /** When boarding equals search, inventory uses a single calendar day (no midnight expansion). */
    public function testRegularTripGeideaPaystatusSearchEqualsBoarding(): void
    {
        $search = '2026-07-14';
        $dates  = [
            'bus_start' => $search,
            'boarding'  => $search,
        ];

        $this->assertSame($dates['boarding'], $search);
        $this->assertSame(
            BookingInventorySupport::buildInventoryMatchDates($search, $search),
            [$search]
        );

        // paystatusgeidea: old code stored boarding, new code stores search — same value.
        $this->assertSame($dates['boarding'], $search);
    }

    /** TBB7P23HFY: admin search 8 Sep + 1 AM AA→EE → jl 8 Sep, ticket 9 Sep. */
    public function testAdminBookingDatesSep8ServiceOneAmPickup(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8581)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8581 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveAdminBookingDates(117, 400, '2026-09-08', 8581);

        $this->assertSame('2026-09-08', $dates['bus_start']);
        $this->assertSame('2026-09-09', $dates['boarding']);
    }

    /** TBPS1V9KXD: admin search 20 Aug + 3 AM BB→GG → jl 20 Aug, ticket 21 Aug. */
    public function testAdminBookingDatesSubtripThreeAmPickup(): void
    {
        try {
            $db = \Config\Database::connect();
            $pickdrop = $db->table('pickdrops')->select('id')->where('id', 8612)->get()->getRow();
            if (! $pickdrop) {
                $this->markTestSkipped('Pickstand 8612 not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $dates = BookingInventorySupport::resolveAdminBookingDates(117, 401, '2026-08-20', 8612);

        $this->assertSame('2026-08-20', $dates['bus_start']);
        $this->assertSame('2026-08-21', $dates['boarding']);
    }

    /** Main subtrip pickdrops may be stored under trips.id (legacy) instead of subtrips.id. */
    public function testMainSubtripPickupStandsResolveLegacyTripId(): void
    {
        try {
            $db = \Config\Database::connect();
            $legacy = $db->table('pickdrops')->select('id')->where('trip_id', 117)->where('type', 1)->where('deleted_at', null)->get()->getRow();
            if (! $legacy) {
                $this->markTestSkipped('Trip 117 legacy main pickdrops not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $helper = new TripJourneyDateHelper();
        $pickups = $helper->listSegmentPickupStandIds(117, 396);

        $this->assertNotEmpty($pickups);
        $this->assertContains((int) $legacy->id, $pickups);
    }

    public function testTrip75MainSubtripHasPickupStandsForSearch(): void
    {
        try {
            $db = \Config\Database::connect();
            $legacy = $db->table('pickdrops')->select('id')->where('trip_id', 75)->where('type', 1)->where('deleted_at', null)->get()->getRow();
            if (! $legacy) {
                $this->markTestSkipped('Trip 75 legacy main pickdrops not in database');
            }
        } catch (\Throwable $e) {
            $this->markTestSkipped('Database not available: ' . $e->getMessage());
        }

        $helper = new TripJourneyDateHelper();
        $pickups = $helper->listSegmentPickupStandIds(75, 236);

        $this->assertNotEmpty($pickups);
        $this->assertContains((int) $legacy->id, $pickups);

        $boardingStands = $helper->pickStandIdsBoardingOnSearchDate(75, 236, '2026-06-03');
        $this->assertNotEmpty($boardingStands);
    }
}
