//
//  ScenarioAPlannerTests.swift
//  SportsTimeTests
//
//  Phase 4: ScenarioAPlanner Tests
//  Scenario A: User provides dates, planner finds games.
//

import Testing
import CoreLocation
@testable import SportsTime

@Suite("ScenarioAPlanner Tests")
struct ScenarioAPlannerTests {

    // MARK: - Test Fixtures

    private let calendar = Calendar.current
    private let planner = ScenarioAPlanner()

    /// Creates a date with specific year/month/day/hour
    private func makeDate(year: Int = 2026, month: Int = 6, day: Int, hour: Int = 19) -> Date {
        var components = DateComponents()
        components.year = year
        components.month = month
        components.day = day
        components.hour = hour
        components.minute = 0
        return calendar.date(from: components)!
    }

    /// Creates a stadium at a known location
    private func makeStadium(
        id: String = "stadium_test_\(UUID().uuidString)",
        city: String,
        lat: Double,
        lon: Double,
        sport: Sport = .mlb
    ) -> Stadium {
        Stadium(
            id: id,
            name: "\(city) Stadium",
            city: city,
            state: "ST",
            latitude: lat,
            longitude: lon,
            capacity: 40000,
            sport: sport
        )
    }

    /// Creates a game at a stadium
    private func makeGame(
        id: String = "game_test_\(UUID().uuidString)",
        stadiumId: String,
        homeTeamId: String = "team_test_\(UUID().uuidString)",
        awayTeamId: String = "team_test_\(UUID().uuidString)",
        dateTime: Date,
        sport: Sport = .mlb
    ) -> Game {
        Game(
            id: id,
            homeTeamId: homeTeamId,
            awayTeamId: awayTeamId,
            stadiumId: stadiumId,
            dateTime: dateTime,
            sport: sport,
            season: "2026"
        )
    }

    /// Creates a PlanningRequest with the given parameters
    private func makePlanningRequest(
        startDate: Date,
        endDate: Date,
        games: [Game],
        stadiums: [String: Stadium],
        teams: [String: Team] = [:],
        allowRepeatCities: Bool = true,
        numberOfDrivers: Int = 1,
        maxDrivingHoursPerDriver: Double = 8.0,
        mustStopLocations: [LocationInput] = []
    ) -> PlanningRequest {
        let preferences = TripPreferences(
            planningMode: .dateRange,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            mustStopLocations: mustStopLocations,
            numberOfDrivers: numberOfDrivers,
            maxDrivingHoursPerDriver: maxDrivingHoursPerDriver,
            allowRepeatCities: allowRepeatCities
        )

        return PlanningRequest(
            preferences: preferences,
            availableGames: games,
            teams: teams,
            stadiums: stadiums
        )
    }

    // MARK: - 4A: Valid Inputs

    @Test("4.1 - Valid date range returns games in range")
    func test_planByDates_ValidDateRange_ReturnsGamesInRange() {
        // Setup: 3 games across nearby cities over 5 days
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let detroitId = "stadium_detroit_\(UUID().uuidString)"

        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
        let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)

        let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee, detroitId: detroit]

        let game1 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
        let game3 = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [game1, game2, game3],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify
        #expect(result.isSuccess, "Should succeed with valid date range and games")
        #expect(!result.options.isEmpty, "Should return at least one itinerary option")

        // All returned games should be within date range
        for option in result.options {
            #expect(option.stops.allSatisfy { !$0.games.isEmpty || $0.city.isEmpty == false },
                   "Each option should have valid stops")
        }
    }

    @Test("4.2 - Single day range returns games on that day")
    func test_planByDates_SingleDayRange_ReturnsGamesOnThatDay() {
        // Setup: Multiple games on a single day at the same stadium
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [stadiumId: stadium]

        // Doubleheader on June 5
        let game1 = makeGame(stadiumId: stadiumId, dateTime: makeDate(day: 5, hour: 13))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: makeDate(day: 5, hour: 19))

        // Game outside the range
        let gameOutside = makeGame(stadiumId: stadiumId, dateTime: makeDate(day: 6, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(day: 5, hour: 0),
            endDate: makeDate(day: 5, hour: 23),
            games: [game1, game2, gameOutside],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify
        #expect(result.isSuccess, "Should succeed for single day range")
        #expect(!result.options.isEmpty, "Should return at least one option")

        // Games in options should only be from June 5
        if let firstOption = result.options.first {
            let gameIds = Set(firstOption.stops.flatMap { $0.games })
            #expect(gameIds.contains(game1.id) || gameIds.contains(game2.id),
                   "Should include games from the single day")
            #expect(!gameIds.contains(gameOutside.id),
                   "Should not include games outside the date range")
        }
    }

    @Test("4.3 - Multi-week range returns multiple games")
    func test_planByDates_MultiWeekRange_ReturnsMultipleGames() {
        // Setup: Games spread across 3 weeks in nearby cities
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let detroitId = "stadium_detroit_\(UUID().uuidString)"
        let clevelandId = "stadium_cleveland_\(UUID().uuidString)"

        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
        let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)
        let cleveland = makeStadium(id: clevelandId, city: "Cleveland", lat: 41.4993, lon: -81.6944)

        let stadiums = [
            chicagoId: chicago,
            milwaukeeId: milwaukee,
            detroitId: detroit,
            clevelandId: cleveland
        ]

        // Games across 3 weeks
        let game1 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 1, hour: 19))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 5, hour: 19))
        let game3 = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 10, hour: 19))
        let game4 = makeGame(stadiumId: clevelandId, dateTime: makeDate(day: 15, hour: 19))
        let game5 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 20, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(day: 1, hour: 0),
            endDate: makeDate(day: 21, hour: 23),
            games: [game1, game2, game3, game4, game5],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify
        #expect(result.isSuccess, "Should succeed for multi-week range")
        #expect(!result.options.isEmpty, "Should return itinerary options")

        // Should have options with multiple games
        let optionWithMultipleGames = result.options.first { $0.totalGames >= 2 }
        #expect(optionWithMultipleGames != nil, "Should have options covering multiple games")
    }

    // MARK: - 4B: Edge Cases

    @Test("4.4 - No games in range returns failure")
    func test_planByDates_NoGamesInRange_ThrowsError() {
        // Setup: Games outside the requested date range
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [stadiumId: stadium]

        // Games in July, but request is for June
        let gameOutside1 = makeGame(stadiumId: stadiumId, dateTime: makeDate(month: 7, day: 10, hour: 19))
        let gameOutside2 = makeGame(stadiumId: stadiumId, dateTime: makeDate(month: 7, day: 15, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(month: 6, day: 1, hour: 0),
            endDate: makeDate(month: 6, day: 30, hour: 23),
            games: [gameOutside1, gameOutside2],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should fail with noGamesInRange
        #expect(!result.isSuccess, "Should fail when no games in range")
        #expect(result.failure?.reason == .noGamesInRange,
               "Should return noGamesInRange failure reason")
    }

    @Test("4.5 - End date before start date returns failure")
    func test_planByDates_EndDateBeforeStartDate_ThrowsError() {
        // Setup: Invalid date range where end < start
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [stadiumId: stadium]

        let game = makeGame(stadiumId: stadiumId, dateTime: makeDate(day: 5, hour: 19))

        // End date before start date
        let request = makePlanningRequest(
            startDate: makeDate(day: 15, hour: 0),  // June 15
            endDate: makeDate(day: 5, hour: 23),    // June 5 (before start)
            games: [game],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should fail with missingDateRange (invalid range)
        #expect(!result.isSuccess, "Should fail when end date is before start date")
        #expect(result.failure?.reason == .missingDateRange,
               "Should return missingDateRange for invalid date range")
    }

    @Test("4.6 - Single game in range returns single game route")
    func test_planByDates_SingleGameInRange_ReturnsSingleGameRoute() {
        // Setup: Only one game in the date range
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [stadiumId: stadium]

        let game = makeGame(stadiumId: stadiumId, dateTime: makeDate(day: 10, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(day: 5, hour: 0),
            endDate: makeDate(day: 15, hour: 23),
            games: [game],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify
        #expect(result.isSuccess, "Should succeed with single game")
        #expect(!result.options.isEmpty, "Should return at least one option")

        if let firstOption = result.options.first {
            #expect(firstOption.totalGames == 1, "Should have exactly 1 game")
            #expect(firstOption.stops.count == 1, "Should have exactly 1 stop")
            #expect(firstOption.stops.first?.games.contains(game.id) == true,
                   "Stop should contain the single game")
        }
    }

    @Test("4.7 - Max games in range handles gracefully", .timeLimit(.minutes(5)))
    func test_planByDates_MaxGamesInRange_HandlesGracefully() {
        // Setup: Generate 10K games using fixture generator
        let config = FixtureGenerator.Configuration(
            seed: 42,
            gameCount: 10000,
            stadiumCount: 30,
            teamCount: 60,
            dateRange: makeDate(day: 1, hour: 0)...makeDate(month: 9, day: 30, hour: 23),
            geographicSpread: .nationwide
        )
        let data = FixtureGenerator.generate(with: config)

        let request = makePlanningRequest(
            startDate: makeDate(day: 1, hour: 0),
            endDate: makeDate(month: 9, day: 30, hour: 23),
            games: data.games,
            stadiums: data.stadiumsById
        )

        // Execute with timing
        let startTime = Date()
        let result = planner.plan(request: request)
        let elapsed = Date().timeIntervalSince(startTime)

        // Verify: Should complete without crash/hang
        #expect(elapsed < TestConstants.performanceTimeout,
               "Should complete within performance timeout")

        // Should produce some result (success or failure is acceptable)
        // The key is that it doesn't crash or hang
        if result.isSuccess {
            #expect(!result.options.isEmpty, "If success, should have options")
        }
        // Failure is also acceptable for extreme scale (e.g., no valid routes)
    }

    // MARK: - 4C: Integration with DAG

    @Test("4.8 - Uses DAG router for routing")
    func test_planByDates_UsesDAGRouterForRouting() {
        // Setup: Games that require DAG routing logic
        // Create games in multiple cities with feasible transitions
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let detroitId = "stadium_detroit_\(UUID().uuidString)"

        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
        let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)

        let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee, detroitId: detroit]

        // Games that can form a sensible route: Chicago → Milwaukee → Detroit
        let game1 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
        let game3 = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [game1, game2, game3],
            stadiums: stadiums
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: DAG router should produce routes
        #expect(result.isSuccess, "Should succeed with routable games")
        #expect(!result.options.isEmpty, "Should produce routes")

        // Verify routes are in chronological order (DAG property)
        for option in result.options {
            // Stops should be in order that respects game times
            var previousGameDate: Date?
            for stop in option.stops {
                if let firstGameId = stop.games.first,
                   let game = [game1, game2, game3].first(where: { $0.id == firstGameId }) {
                    if let prev = previousGameDate {
                        #expect(game.startTime >= prev,
                               "Games should be in chronological order (DAG property)")
                    }
                    previousGameDate = game.startTime
                }
            }
        }
    }

    @Test("4.9 - Respects driver constraints")
    func test_planByDates_RespectsDriverConstraints() {
        // Setup: Games that would require excessive daily driving if constraints are loose
        let nycId = "stadium_nyc_\(UUID().uuidString)"
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"

        // NYC to Chicago is ~790 miles (~13 hours of driving)
        let nyc = makeStadium(id: nycId, city: "New York", lat: 40.7128, lon: -73.9352)
        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        let stadiums = [nycId: nyc, chicagoId: chicago]

        // Games on consecutive days - can't drive 790 miles in 8 hours (single driver)
        let game1 = makeGame(stadiumId: nycId, dateTime: makeDate(day: 5, hour: 14))
        let game2 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 6, hour: 19))

        // Test with strict constraints (1 driver, 8 hours max)
        let strictRequest = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 7, hour: 23),
            games: [game1, game2],
            stadiums: stadiums,
            numberOfDrivers: 1,
            maxDrivingHoursPerDriver: 8.0
        )

        let strictResult = planner.plan(request: strictRequest)

        // With strict constraints, should NOT have a route with both games on consecutive days
        if strictResult.isSuccess {
            let hasConsecutiveDayRoute = strictResult.options.contains { option in
                option.totalGames == 2 && option.stops.count == 2
            }
            // If there's a 2-game route, verify it has adequate travel time
            if hasConsecutiveDayRoute, let twoGameOption = strictResult.options.first(where: { $0.totalGames == 2 }) {
                // With only 1 day between games, ~13 hours of driving is too much for 8hr/day limit
                // The route should either not exist or have adequate travel days
                let totalHours = twoGameOption.totalDrivingHours
                let daysAvailable = 1.0 // Only 1 day between games
                let hoursPerDay = totalHours / daysAvailable

                // This assertion is soft - the router may reject this route entirely
                #expect(hoursPerDay <= 8.0 || !hasConsecutiveDayRoute,
                       "Route should respect daily driving limits")
            }
        }

        // Test with relaxed constraints (2 drivers = 16 hours max per day)
        let relaxedRequest = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 7, hour: 23),
            games: [game1, game2],
            stadiums: stadiums,
            numberOfDrivers: 2,
            maxDrivingHoursPerDriver: 8.0
        )

        let relaxedResult = planner.plan(request: relaxedRequest)

        // With 2 drivers (16 hours/day), the trip becomes more feasible
        // Note: 790 miles at 60mph is ~13 hours, which fits in 16 hours
        if relaxedResult.isSuccess {
            // Should have more routing options with relaxed constraints
            #expect(relaxedResult.options.count >= 1,
                   "Should have options with relaxed driver constraints")
        }
    }

    // MARK: - 4D: Must-Stop Filtering (Issues #4 & #8)

    @Test("4.10 - Must-stop filters to games in that city")
    func test_planByDates_MustStop_FiltersToGamesInCity() {
        // Setup: Games in Chicago, Milwaukee, Detroit
        // Must-stop = Chicago → should only return Chicago games
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let detroitId = "stadium_detroit_\(UUID().uuidString)"

        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
        let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)

        let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee, detroitId: detroit]

        let chicagoGame = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
        let milwaukeeGame = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
        let detroitGame = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))

        let mustStop = LocationInput(name: "Chicago")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [chicagoGame, milwaukeeGame, detroitGame],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should succeed and ONLY include Chicago games
        #expect(result.isSuccess, "Should succeed with must-stop in Chicago")
        #expect(!result.options.isEmpty, "Should return at least one option")

        // All games in result should be in Chicago only
        for option in result.options {
            let allGameIds = Set(option.stops.flatMap { $0.games })
            #expect(allGameIds.contains(chicagoGame.id), "Should include Chicago game")
            #expect(!allGameIds.contains(milwaukeeGame.id), "Should NOT include Milwaukee game")
            #expect(!allGameIds.contains(detroitGame.id), "Should NOT include Detroit game")
        }
    }

    @Test("4.11 - Must-stop with no matching games returns failure")
    func test_planByDates_MustStop_NoMatchingGames_ReturnsFailure() {
        // Setup: Games only in Milwaukee and Detroit
        // Must-stop = Chicago → no games there, should fail
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let detroitId = "stadium_detroit_\(UUID().uuidString)"

        let milwaukee = makeStadium(id: milwaukeeId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)
        let detroit = makeStadium(id: detroitId, city: "Detroit", lat: 42.3314, lon: -83.0458)

        let stadiums = [milwaukeeId: milwaukee, detroitId: detroit]

        let milwaukeeGame = makeGame(stadiumId: milwaukeeId, dateTime: makeDate(day: 7, hour: 19))
        let detroitGame = makeGame(stadiumId: detroitId, dateTime: makeDate(day: 9, hour: 19))

        let mustStop = LocationInput(name: "Chicago")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [milwaukeeGame, detroitGame],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should fail with noGamesInRange (no home games in Chicago)
        #expect(!result.isSuccess, "Should fail when no games in must-stop city")
        #expect(result.failure?.reason == .noGamesInRange,
               "Should return noGamesInRange failure")
    }

    @Test("4.12 - Must-stop only returns HOME games (Issue #8)")
    func test_planByDates_MustStop_OnlyReturnsHomeGames() {
        // Setup: Cubs home game in Chicago + Cubs away game in Milwaukee (playing at Milwaukee)
        // Must-stop = Chicago → should ONLY return the Chicago home game
        // This tests Issue #8: "Must stop needs to be home team"
        let chicagoStadiumId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeStadiumId = "stadium_milwaukee_\(UUID().uuidString)"
        let cubsTeamId = "team_cubs_\(UUID().uuidString)"
        let brewersTeamId = "team_brewers_\(UUID().uuidString)"

        let wrigleyField = makeStadium(id: chicagoStadiumId, city: "Chicago", lat: 41.9484, lon: -87.6553)
        let millerPark = makeStadium(id: milwaukeeStadiumId, city: "Milwaukee", lat: 43.0280, lon: -87.9712)

        let stadiums = [chicagoStadiumId: wrigleyField, milwaukeeStadiumId: millerPark]

        // Cubs HOME game at Wrigley (Chicago)
        let cubsHomeGame = makeGame(
            stadiumId: chicagoStadiumId,
            homeTeamId: cubsTeamId,
            awayTeamId: brewersTeamId,
            dateTime: makeDate(day: 5, hour: 19)
        )

        // Cubs AWAY game at Miller Park (Milwaukee) - Cubs are playing but NOT at home
        let cubsAwayGame = makeGame(
            stadiumId: milwaukeeStadiumId,
            homeTeamId: brewersTeamId,
            awayTeamId: cubsTeamId,
            dateTime: makeDate(day: 7, hour: 19)
        )

        let mustStop = LocationInput(name: "Chicago")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [cubsHomeGame, cubsAwayGame],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should succeed with ONLY the Chicago home game
        #expect(result.isSuccess, "Should succeed with Chicago home game")
        #expect(!result.options.isEmpty, "Should return at least one option")

        // The away game in Milwaukee should NOT be included even though Cubs are playing
        for option in result.options {
            let allGameIds = Set(option.stops.flatMap { $0.games })
            #expect(allGameIds.contains(cubsHomeGame.id), "Should include Cubs HOME game in Chicago")
            #expect(!allGameIds.contains(cubsAwayGame.id), "Should NOT include Cubs AWAY game in Milwaukee")
        }
    }

    @Test("4.13 - Must-stop with partial city name match works")
    func test_planByDates_MustStop_PartialCityMatch_Works() {
        // Setup: User types "Chicago" but stadium city is "Chicago, IL"
        // Should still match via contains
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [chicagoId: chicago]

        let game = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))

        // User might type partial name
        let mustStop = LocationInput(name: "Chicago, IL")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [game],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Should still find Chicago games with partial match
        #expect(result.isSuccess, "Should succeed with partial city name match")
        #expect(!result.options.isEmpty, "Should return options")
    }

    @Test("4.14 - Must-stop case insensitive")
    func test_planByDates_MustStop_CaseInsensitive() {
        // Setup: Must-stop = "CHICAGO" (uppercase) should match "Chicago"
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [chicagoId: chicago]

        let game = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))

        let mustStop = LocationInput(name: "CHICAGO")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [game],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: Case insensitive match
        #expect(result.isSuccess, "Should succeed with case-insensitive match")
    }

    @Test("4.15 - Multiple games in must-stop city all included")
    func test_planByDates_MustStop_MultipleGamesInCity_AllIncluded() {
        // Setup: Multiple games in Chicago on different days
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let chicago = makeStadium(id: chicagoId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let stadiums = [chicagoId: chicago]

        let game1 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 5, hour: 19))
        let game2 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 7, hour: 13))
        let game3 = makeGame(stadiumId: chicagoId, dateTime: makeDate(day: 9, hour: 19))

        let mustStop = LocationInput(name: "Chicago")

        let request = makePlanningRequest(
            startDate: makeDate(day: 4, hour: 0),
            endDate: makeDate(day: 10, hour: 23),
            games: [game1, game2, game3],
            stadiums: stadiums,
            mustStopLocations: [mustStop]
        )

        // Execute
        let result = planner.plan(request: request)

        // Verify: All Chicago games should be included
        #expect(result.isSuccess, "Should succeed with multiple games in must-stop city")

        if let option = result.options.first {
            let allGameIds = Set(option.stops.flatMap { $0.games })
            #expect(allGameIds.contains(game1.id), "Should include first Chicago game")
            #expect(allGameIds.contains(game2.id), "Should include second Chicago game")
            #expect(allGameIds.contains(game3.id), "Should include third Chicago game")
        }
    }
}
