//
//  GameDAGRouterTests.swift
//  SportsTimeTests
//
//  Phase 2: GameDAGRouter Tests
//  The "scary to touch" component — extensive edge case coverage.
//

import Testing
import CoreLocation
@testable import SportsTime

@Suite("GameDAGRouter Tests")
struct GameDAGRouterTests {

    // MARK: - Test Fixtures

    private let calendar = Calendar.current

    // Standard game times (7pm local)
    private func gameDate(daysFromNow: Int, hour: Int = 19) -> Date {
        let baseDate = calendar.date(from: DateComponents(year: 2026, month: 6, day: 1))!
        var components = calendar.dateComponents([.year, .month, .day], from: baseDate)
        components.day! += daysFromNow
        components.hour = hour
        components.minute = 0
        return calendar.date(from: components)!
    }

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

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

    // MARK: - 2A: Empty & Single-Element Cases

    @Test("2.1 - Empty games returns empty array")
    func test_findRoutes_EmptyGames_ReturnsEmptyArray() {
        let routes = GameDAGRouter.findRoutes(
            games: [],
            stadiums: [:],
            constraints: .default
        )

        #expect(routes.isEmpty, "Expected empty array for empty games input")
    }

    @Test("2.2 - Single game returns single route")
    func test_findRoutes_SingleGame_ReturnsSingleRoute() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))

        let routes = GameDAGRouter.findRoutes(
            games: [game],
            stadiums: [stadiumId: stadium],
            constraints: .default
        )

        #expect(routes.count == 1, "Expected exactly 1 route for single game")
        #expect(routes.first?.count == 1, "Route should contain exactly 1 game")
        #expect(routes.first?.first?.id == game.id, "Route should contain the input game")
    }

    @Test("2.3 - Single game with matching anchor returns single route")
    func test_findRoutes_SingleGame_WithMatchingAnchor_ReturnsSingleRoute() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))

        let routes = GameDAGRouter.findRoutes(
            games: [game],
            stadiums: [stadiumId: stadium],
            constraints: .default,
            anchorGameIds: [game.id]
        )

        #expect(routes.count == 1, "Expected 1 route when anchor matches the only game")
        #expect(routes.first?.contains(where: { $0.id == game.id }) == true)
    }

    @Test("2.4 - Single game with non-matching anchor returns empty")
    func test_findRoutes_SingleGame_WithNonMatchingAnchor_ReturnsEmpty() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let game = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let nonExistentAnchor = "stadium_nonexistent_\(UUID().uuidString)"

        let routes = GameDAGRouter.findRoutes(
            games: [game],
            stadiums: [stadiumId: stadium],
            constraints: .default,
            anchorGameIds: [nonExistentAnchor]
        )

        #expect(routes.isEmpty, "Expected empty when anchor doesn't match any game")
    }

    // MARK: - 2B: Two-Game Cases

    @Test("2.5 - Two games with feasible transition returns both in order")
    func test_findRoutes_TwoGames_FeasibleTransition_ReturnsBothInOrder() {
        // Chicago to Milwaukee is ~90 miles - easily feasible
        let chicagoStadiumId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeStadiumId = "stadium_milwaukee_\(UUID().uuidString)"

        let chicagoStadium = makeStadium(id: chicagoStadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)
        let milwaukeeStadium = makeStadium(id: milwaukeeStadiumId, city: "Milwaukee", lat: 43.0389, lon: -87.9065)

        let game1 = makeGame(stadiumId: chicagoStadiumId, dateTime: gameDate(daysFromNow: 1, hour: 14)) // Day 1, 2pm
        let game2 = makeGame(stadiumId: milwaukeeStadiumId, dateTime: gameDate(daysFromNow: 2, hour: 19)) // Day 2, 7pm

        let stadiums = [chicagoStadiumId: chicagoStadium, milwaukeeStadiumId: milwaukeeStadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should have at least one route with both games
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Expected a route containing both games")

        if let route = routeWithBoth {
            #expect(route[0].id == game1.id, "First game should be Chicago (earlier)")
            #expect(route[1].id == game2.id, "Second game should be Milwaukee (later)")
        }
    }

    @Test("2.6 - Two games with infeasible transition returns separate routes")
    func test_findRoutes_TwoGames_InfeasibleTransition_ReturnsSeparateRoutes() {
        // NYC to LA on same day is infeasible
        let nycStadiumId = "stadium_nyc_\(UUID().uuidString)"
        let laStadiumId = "stadium_la_\(UUID().uuidString)"

        let nycStadium = makeStadium(id: nycStadiumId, city: "New York", lat: 40.7128, lon: -73.9352)
        let laStadium = makeStadium(id: laStadiumId, city: "Los Angeles", lat: 34.0522, lon: -118.2437)

        // Games on same day, 5 hours apart (can't drive 2500 miles in 5 hours)
        let game1 = makeGame(stadiumId: nycStadiumId, dateTime: gameDate(daysFromNow: 1, hour: 13)) // 1pm
        let game2 = makeGame(stadiumId: laStadiumId, dateTime: gameDate(daysFromNow: 1, hour: 21)) // 9pm

        let stadiums = [nycStadiumId: nycStadium, laStadiumId: laStadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should NOT have a route with both games (infeasible)
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth == nil, "Should not have a combined route for infeasible transition")

        // Should have separate single-game routes
        let singleGameRoutes = routes.filter { $0.count == 1 }
        #expect(singleGameRoutes.count >= 2, "Should have separate routes for each game")
    }

    @Test("2.7 - Two games same stadium same day (doubleheader) succeeds")
    func test_findRoutes_TwoGames_SameStadiumSameDay_Succeeds() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        // Doubleheader: 1pm and 7pm same day, same stadium
        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1, hour: 13))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1, hour: 19))

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should have a route with both games
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Doubleheader at same stadium should be feasible")

        if let route = routeWithBoth {
            #expect(route[0].startTime < route[1].startTime, "Games should be in chronological order")
        }
    }

    // MARK: - 2C: Anchor Game Constraints

    @Test("2.8 - With anchors only returns routes containing all anchors")
    func test_findRoutes_WithAnchors_OnlyReturnsRoutesContainingAllAnchors() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 2))
        let game3 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 3))

        let stadiums = [stadiumId: stadium]
        let anchor = game2.id

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2, game3],
            stadiums: stadiums,
            constraints: .default,
            anchorGameIds: [anchor]
        )

        // All routes must contain the anchor game
        for route in routes {
            let containsAnchor = route.contains { $0.id == anchor }
            #expect(containsAnchor, "Every route must contain the anchor game")
        }
    }

    @Test("2.9 - Impossible anchors returns empty")
    func test_findRoutes_ImpossibleAnchors_ReturnsEmpty() {
        // Two anchors at opposite ends of country on same day - impossible to attend both
        let nycStadiumId = "stadium_nyc_\(UUID().uuidString)"
        let laStadiumId = "stadium_la_\(UUID().uuidString)"

        let nycStadium = makeStadium(id: nycStadiumId, city: "New York", lat: 40.7128, lon: -73.9352)
        let laStadium = makeStadium(id: laStadiumId, city: "Los Angeles", lat: 34.0522, lon: -118.2437)

        // Same day, same time - physically impossible
        let game1 = makeGame(stadiumId: nycStadiumId, dateTime: gameDate(daysFromNow: 1, hour: 19))
        let game2 = makeGame(stadiumId: laStadiumId, dateTime: gameDate(daysFromNow: 1, hour: 19))

        let stadiums = [nycStadiumId: nycStadium, laStadiumId: laStadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default,
            anchorGameIds: [game1.id, game2.id] // Both are anchors
        )

        #expect(routes.isEmpty, "Should return empty for impossible anchor combination")
    }

    @Test("2.10 - Multiple anchors route must contain all")
    func test_findRoutes_MultipleAnchors_RouteMustContainAll() {
        // Three games in nearby cities over 3 days - all feasible
        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 game1 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 2))
        let game3 = makeGame(stadiumId: detroitId, dateTime: gameDate(daysFromNow: 3))

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

        // Make game1 and game3 anchors
        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2, game3],
            stadiums: stadiums,
            constraints: .default,
            anchorGameIds: [game1.id, game3.id]
        )

        #expect(!routes.isEmpty, "Should find routes with both anchors")

        for route in routes {
            let hasGame1 = route.contains { $0.id == game1.id }
            let hasGame3 = route.contains { $0.id == game3.id }
            #expect(hasGame1 && hasGame3, "Every route must contain both anchor games")
        }
    }

    // MARK: - 2D: Repeat Cities Toggle

    @Test("2.11 - Allow repeat cities same city multiple days allowed")
    func test_findRoutes_AllowRepeatCities_SameCityMultipleDays_Allowed() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        // Three games in Chicago over 3 days
        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 2))
        let game3 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 3))

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2, game3],
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: true
        )

        // Should have routes with all 3 games (same city allowed)
        let routeWithAll = routes.first { $0.count == 3 }
        #expect(routeWithAll != nil, "Should allow visiting same city multiple days when repeat cities enabled")
    }

    @Test("2.12 - Disallow repeat cities skips second visit")
    func test_findRoutes_DisallowRepeatCities_SkipsSecondVisit() {
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(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)

        // Day 1: Chicago, Day 2: Milwaukee, Day 3: Back to Chicago
        let game1 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 2))
        let game3 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 3)) // Return to Chicago

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

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2, game3],
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: false
        )

        // Should NOT have a route with both Chicago games
        for route in routes {
            let chicagoGames = route.filter { stadiums[$0.stadiumId]?.city == "Chicago" }
            #expect(chicagoGames.count <= 1, "Should not repeat Chicago when repeat cities disabled")
        }
    }

    @Test("2.13 - Disallow repeat cities only option is repeat overrides with warning")
    func test_findRoutes_DisallowRepeatCities_OnlyOptionIsRepeat_OverridesWithWarning() {
        // When only games available are in the same city, we still need to produce routes
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        // Only Chicago games available
        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 2))

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: false
        )

        // Should still return single-game routes even with repeat cities disabled
        #expect(!routes.isEmpty, "Should return routes even when only option is repeat city")

        // Note: TDD defines Trip.warnings property (test 2.13 in plan)
        // For now, we verify routes exist; warning system will be added when implementing
    }

    // MARK: - 2E: Driving Constraints

    @Test("2.14 - Exceeds max daily driving transition rejected")
    func test_findRoutes_ExceedsMaxDailyDriving_TransitionRejected() {
        // NYC to Denver is ~1,800 miles, way over 8 hours of driving (480 miles at 60mph)
        let nycId = "stadium_nyc_\(UUID().uuidString)"
        let denverId = "stadium_denver_\(UUID().uuidString)"

        let nyc = makeStadium(id: nycId, city: "New York", lat: 40.7128, lon: -73.9352)
        let denver = makeStadium(id: denverId, city: "Denver", lat: 39.7392, lon: -104.9903)

        // Games on consecutive days - can't drive 1800 miles in one day
        let game1 = makeGame(stadiumId: nycId, dateTime: gameDate(daysFromNow: 1, hour: 14))
        let game2 = makeGame(stadiumId: denverId, dateTime: gameDate(daysFromNow: 2, hour: 19))

        let stadiums = [nycId: nyc, denverId: denver]

        // Use strict constraints (8 hours max)
        let strictConstraints = DrivingConstraints(numberOfDrivers: 1, maxHoursPerDriverPerDay: 8.0)

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: strictConstraints
        )

        // Should not have a combined route (distance too far for 1 day)
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth == nil, "Should reject transition exceeding max daily driving")
    }

    @Test("2.15 - Multi-day drive allowed if within daily limits")
    func test_findRoutes_MultiDayDrive_Allowed_IfWithinDailyLimits() {
        // NYC to Chicago is ~790 miles - doable over multiple days
        let nycId = "stadium_nyc_\(UUID().uuidString)"
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"

        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)

        // Games 3 days apart - enough time to drive 790 miles
        let game1 = makeGame(stadiumId: nycId, dateTime: gameDate(daysFromNow: 1, hour: 14))
        let game2 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 4, hour: 19))

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

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should have a route with both (multi-day driving allowed)
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Should allow multi-day drive when time permits")
    }

    @Test("2.16 - Same day different stadiums checks available time")
    func test_findRoutes_SameDayDifferentStadiums_ChecksAvailableTime() {
        // Chicago to Milwaukee is ~90 miles (~1.5 hours driving)
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(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)

        // Same day: Chicago at 1pm, Milwaukee at 7pm (6 hours apart - feasible)
        let game1 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 1, hour: 13))
        let game2 = makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 1, hour: 19))

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

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should be feasible (1pm game + 3hr duration + 1.5hr drive = arrives ~5:30pm for 7pm game)
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Should allow same-day travel when time permits")

        // Now test too tight timing
        let game3 = makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 2, hour: 16)) // 4pm
        let game4 = makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 2, hour: 17)) // 5pm (only 1 hr apart)

        let routes2 = GameDAGRouter.findRoutes(
            games: [game3, game4],
            stadiums: stadiums,
            constraints: .default
        )

        let tooTightRoute = routes2.first { $0.count == 2 }
        #expect(tooTightRoute == nil, "Should reject same-day travel when not enough time")
    }

    // MARK: - 2F: Calendar Day Logic

    @Test("2.17 - Max day lookahead respects limit")
    func test_findRoutes_MaxDayLookahead_RespectsLimit() {
        // Games more than 5 days apart should not connect directly
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 8)) // 7 days later

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // With max lookahead of 5, these shouldn't directly connect
        // (Though they might still appear in separate routes)
        let routeWithBoth = routes.first { $0.count == 2 }

        // Note: Implementation uses maxDayLookahead = 5
        // Games 7 days apart may not connect directly
        // This test verifies the behavior
        if routeWithBoth != nil {
            // If they do connect, verify they're in order
            #expect(routeWithBoth![0].startTime < routeWithBoth![1].startTime)
        }
    }

    @Test("2.18 - DST transition handles correctly")
    func test_findRoutes_DSTTransition_HandlesCorrectly() {
        // Test around DST transition (March 9, 2026 - spring forward)
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        // Create dates around DST transition
        var components1 = DateComponents()
        components1.year = 2026
        components1.month = 3
        components1.day = 8 // Day before spring forward
        components1.hour = 19
        let preDST = calendar.date(from: components1)!

        var components2 = DateComponents()
        components2.year = 2026
        components2.month = 3
        components2.day = 9 // Spring forward day
        components2.hour = 19
        let postDST = calendar.date(from: components2)!

        let game1 = makeGame(stadiumId: stadiumId, dateTime: preDST)
        let game2 = makeGame(stadiumId: stadiumId, dateTime: postDST)

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Should handle DST correctly - both games should be connectable
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Should handle DST transition correctly")
    }

    @Test("2.19 - Midnight game assigns to correct day")
    func test_findRoutes_MidnightGame_AssignsToCorrectDay() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        // Game at 12:05 AM belongs to the new day
        var components = DateComponents()
        components.year = 2026
        components.month = 6
        components.day = 2
        components.hour = 0
        components.minute = 5
        let midnightGame = calendar.date(from: components)!

        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1, hour: 19)) // Day 1, 7pm
        let game2 = makeGame(stadiumId: stadiumId, dateTime: midnightGame) // Day 2, 12:05am

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default
        )

        // Midnight game should be on day 2, making transition feasible
        let routeWithBoth = routes.first { $0.count == 2 }
        #expect(routeWithBoth != nil, "Midnight game should be assigned to correct calendar day")
    }

    // MARK: - 2G: Diversity Selection

    @Test("2.20 - Select diverse routes includes short and long trips")
    func test_selectDiverseRoutes_ShortAndLongTrips_BothRepresented() {
        // Create a mix of games over a week
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        var games: [Game] = []
        for day in 1...7 {
            games.append(makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: day)))
        }

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: games,
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: true
        )

        // Should have both short (2-3 game) and long (5+ game) routes
        let shortRoutes = routes.filter { $0.count <= 3 }
        let longRoutes = routes.filter { $0.count >= 5 }

        #expect(!shortRoutes.isEmpty, "Should include short trip options")
        #expect(!longRoutes.isEmpty, "Should include long trip options")
    }

    @Test("2.21 - Select diverse routes includes high and low mileage")
    func test_selectDiverseRoutes_HighAndLowMileage_BothRepresented() {
        // Create games in both nearby and distant cities
        let chicagoId = "stadium_chicago_\(UUID().uuidString)"
        let milwaukeeId = "stadium_milwaukee_\(UUID().uuidString)"
        let laId = "stadium_la_\(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 la = makeStadium(id: laId, city: "Los Angeles", lat: 34.0522, lon: -118.2437)

        let games = [
            makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 1)),
            makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 2)),
            makeGame(stadiumId: laId, dateTime: gameDate(daysFromNow: 8)), // Far away, needs time
        ]

        let stadiums = [chicagoId: chicago, milwaukeeId: milwaukee, laId: la]

        let routes = GameDAGRouter.findRoutes(
            games: games,
            stadiums: stadiums,
            constraints: .default
        )

        // Should have routes with varying mileage
        #expect(!routes.isEmpty, "Should produce diverse mileage routes")
    }

    @Test("2.22 - Select diverse routes includes few and many cities")
    func test_selectDiverseRoutes_FewAndManyCities_BothRepresented() {
        // Create games across multiple cities
        let cities = [
            ("Chicago", 41.8781, -87.6298),
            ("Milwaukee", 43.0389, -87.9065),
            ("Detroit", 42.3314, -83.0458),
            ("Cleveland", 41.4993, -81.6944),
        ]

        var stadiums: [String: Stadium] = [:]
        var games: [Game] = []

        for (index, city) in cities.enumerated() {
            let stadiumId = "stadium_test_\(UUID().uuidString)"
            stadiums[stadiumId] = makeStadium(id: stadiumId, city: city.0, lat: city.1, lon: city.2)
            games.append(makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: index + 1)))
        }

        let routes = GameDAGRouter.findRoutes(
            games: games,
            stadiums: stadiums,
            constraints: .default
        )

        // Should have routes with varying city counts
        let cityCounts = routes.map { route in
            Set(route.compactMap { stadiums[$0.stadiumId]?.city }).count
        }

        let minCities = cityCounts.min() ?? 0
        let maxCities = cityCounts.max() ?? 0

        #expect(minCities < maxCities || routes.count <= 1, "Should have routes with varying city counts")
    }

    @Test("2.23 - Select diverse routes deduplicates")
    func test_selectDiverseRoutes_DuplicateRoutes_Deduplicated() {
        let stadiumId = "stadium_test_\(UUID().uuidString)"
        let stadium = makeStadium(id: stadiumId, city: "Chicago", lat: 41.8781, lon: -87.6298)

        let game1 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 1))
        let game2 = makeGame(stadiumId: stadiumId, dateTime: gameDate(daysFromNow: 2))

        let stadiums = [stadiumId: stadium]

        let routes = GameDAGRouter.findRoutes(
            games: [game1, game2],
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: true
        )

        // Check for duplicates
        var seen = Set<String>()
        for route in routes {
            let key = route.map { $0.id }.joined(separator: "-")
            #expect(!seen.contains(key), "Routes should be deduplicated")
            seen.insert(key)
        }
    }

    // MARK: - 2H: Cycle Handling

    @Test("2.24 - Graph with potential cycle handles silently")
    func test_findRoutes_GraphWithPotentialCycle_HandlesSilently() {
        // Create a scenario where a naive algorithm might get stuck in a loop
        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)

        // Multiple games at each city over several days (potential for cycles)
        let games = [
            makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 1)),
            makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 2)),
            makeGame(stadiumId: chicagoId, dateTime: gameDate(daysFromNow: 3)), // Back to Chicago
            makeGame(stadiumId: detroitId, dateTime: gameDate(daysFromNow: 4)),
            makeGame(stadiumId: milwaukeeId, dateTime: gameDate(daysFromNow: 5)), // Back to Milwaukee
        ]

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

        // Should complete without hanging or infinite loop
        let routes = GameDAGRouter.findRoutes(
            games: games,
            stadiums: stadiums,
            constraints: .default,
            allowRepeatCities: true
        )

        // Just verify it completes and returns valid routes
        #expect(routes.allSatisfy { !$0.isEmpty }, "All routes should be non-empty")

        // Verify chronological order in each route
        for route in routes {
            for i in 0..<(route.count - 1) {
                #expect(route[i].startTime < route[i + 1].startTime, "Games should be in chronological order")
            }
        }
    }

    // MARK: - 2I: Beam Search Behavior

    @Test("2.25 - Large dataset scales beam width")
    func test_findRoutes_LargeDataset_ScalesBeamWidth() {
        // Generate a large dataset (use fixture generator)
        let data = FixtureGenerator.generate(with: .medium) // 500 games

        let routes = GameDAGRouter.findRoutes(
            games: data.games,
            stadiums: data.stadiumsById,
            constraints: .default
        )

        // Should complete and return routes
        #expect(!routes.isEmpty, "Should produce routes for large dataset")

        // Verify routes are valid
        for route in routes {
            for i in 0..<(route.count - 1) {
                #expect(route[i].startTime <= route[i + 1].startTime, "Games should be chronologically ordered")
            }
        }
    }

    @Test("2.26 - Early termination triggers when beam full")
    func test_findRoutes_EarlyTermination_TriggersWhenBeamFull() {
        // Generate a dataset that would take very long without early termination
        let config = FixtureGenerator.Configuration(
            seed: 42,
            gameCount: 100,
            stadiumCount: 20,
            teamCount: 20,
            geographicSpread: .regional
        )
        let data = FixtureGenerator.generate(with: config)

        let startTime = Date()
        let routes = GameDAGRouter.findRoutes(
            games: data.games,
            stadiums: data.stadiumsById,
            constraints: .default,
            beamWidth: 50 // Moderate beam width
        )
        let elapsed = Date().timeIntervalSince(startTime)

        // Should complete in reasonable time (< 30 seconds indicates early termination is working)
        #expect(elapsed < TestConstants.hangTimeout, "Should complete before hang timeout (early termination)")
        #expect(!routes.isEmpty, "Should produce routes")
    }
}
