//
//  ScenarioEPlannerTests.swift
//  SportsTimeTests
//
//  TDD specification tests for ScenarioEPlanner (Team-First planning mode).
//
//  Team-First mode allows users to select multiple teams and finds optimal
//  trip windows across the season where all selected teams have home games.
//

import Testing
import CoreLocation
@testable import SportsTime

@Suite("ScenarioEPlanner")
struct ScenarioEPlannerTests {

    // MARK: - Test Data

    private let planner = ScenarioEPlanner()

    // East Coast coordinates
    private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
    private let bostonCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
    private let phillyCoord = CLLocationCoordinate2D(latitude: 39.9526, longitude: -75.1652)

    // Central coordinates
    private let chicagoCoord = CLLocationCoordinate2D(latitude: 41.8827, longitude: -87.6233)

    // West Coast coordinates
    private let laCoord = CLLocationCoordinate2D(latitude: 34.0430, longitude: -118.2673)

    // MARK: - E1. Window Generator Tests

    @Test("generateValidWindows: 2 teams with 4-day window finds valid windows")
    func generateValidWindows_2Teams4DayWindow_findsValidWindows() {
        // Setup: 2 teams (simpler case), each with home games in overlapping time periods
        // NYC and Boston are only ~4 hours apart, making routes feasible
        //
        // With 2 teams, window duration = 4 days.
        // The window algorithm checks: windowEnd <= latestGameDay + 1
        // So with games on day 1 and day 4: latestDay=4, windowEnd=5 <= day 5 (4+1) - valid!
        let calendar = Calendar.current
        let baseDate = calendar.startOfDay(for: Date())

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Create games with evening times (7 PM) to give ample travel time
        // Day 1 evening: Yankees home game
        // Day 4 evening: Red Sox home game (spans 4 days, fits in 4-day window)
        let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
        let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: day1Evening
        )
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: day4Evening
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        // Should succeed - NYC to Boston is easily drivable
        guard case .success(let options) = result else {
            Issue.record("Expected success with overlapping home games for nearby cities")
            return
        }

        #expect(!options.isEmpty, "Should find valid windows when all teams have overlapping home games")
    }

    @Test("generateValidWindows: window with only 2 of 3 teams excluded")
    func generateValidWindows_windowMissingTeam_excluded() {
        // Setup: 3 teams selected, but games are spread so no single window covers all 3
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)
        let phillyStadium = makeStadium(id: "philly", city: "Philadelphia", coordinate: phillyCoord)

        // Yankees game on day 1
        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )
        // Red Sox game on day 2
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: baseDate.addingTimeInterval(86400 * 2)
        )
        // Phillies game on day 20 (way outside any 6-day window with the others)
        let philliesGame = makeGame(
            id: "phillies-home",
            homeTeamId: "phillies",
            awayTeamId: "opponent",
            stadiumId: "philly",
            dateTime: baseDate.addingTimeInterval(86400 * 20)
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox", "phillies"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame, philliesGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox"),
                "phillies": makeTeam(id: "phillies", name: "Phillies")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium, "philly": phillyStadium]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when no valid window covers all teams")
            return
        }

        #expect(failure.reason == .noValidRoutes, "Should return noValidRoutes when no window covers all teams")
    }

    @Test("generateValidWindows: empty season returns empty")
    func generateValidWindows_emptySeason_returnsEmpty() {
        // Setup: No games available
        let baseDate = Date()

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [],  // No games
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: [:]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure with no games")
            return
        }

        #expect(failure.reason == .noGamesInRange, "Should return noGamesInRange when no home games exist")
    }

    @Test("generateValidWindows: sampling works when more than 50 windows")
    func generateValidWindows_manyWindows_samplesProperly() {
        // Setup: Create many overlapping games so there are >50 valid windows
        let calendar = Calendar.current
        let baseDate = calendar.startOfDay(for: Date())

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Create games on alternating days - Yankees on even days, Red Sox on odd days
        // This ensures games don't conflict and routes are feasible
        // With 90 days of games, we get ~45 games per team with plenty of valid 4-day windows
        var games: [Game] = []
        for day in 0..<90 {
            let dayDate = baseDate.addingTimeInterval(Double(86400 * day))
            let eveningTime = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: dayDate)!

            if day % 2 == 0 {
                // Yankees home games on even days
                let yankeesGame = makeGame(
                    id: "yankees-\(day)",
                    homeTeamId: "yankees",
                    awayTeamId: "opponent",
                    stadiumId: "nyc",
                    dateTime: eveningTime
                )
                games.append(yankeesGame)
            } else {
                // Red Sox home games on odd days
                let redsoxGame = makeGame(
                    id: "redsox-\(day)",
                    homeTeamId: "redsox",
                    awayTeamId: "opponent",
                    stadiumId: "boston",
                    dateTime: eveningTime
                )
                games.append(redsoxGame)
            }
        }

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 90),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: games,
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        // Should succeed and return results (sampling internal behavior)
        guard case .success(let options) = result else {
            Issue.record("Expected success with many valid windows")
            return
        }

        #expect(!options.isEmpty, "Should return options even with many windows (sampling applies internally)")
        #expect(options.count <= 10, "Should return at most 10 results")
    }

    // MARK: - E2. ScenarioEPlanner Unit Tests

    @Test("plan: returns PlanningResult with routes")
    func plan_validRequest_returnsPlanningResultWithRoutes() {
        let calendar = Calendar.current
        let baseDate = calendar.startOfDay(for: Date())

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Use evening game times to allow travel during the day
        // Games must span 4 days for 2-team window (day 1 to day 4)
        let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
        let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: day1Evening
        )
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: day4Evening
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        guard case .success(let options) = result else {
            Issue.record("Expected success with valid team-first request")
            return
        }

        #expect(!options.isEmpty, "Should return route options")
        #expect(options.first?.stops.count ?? 0 > 0, "Each option should have stops")
    }

    @Test("plan: all routes include all selected teams")
    func plan_allRoutesIncludeAllSelectedTeams() {
        // Use just 2 teams for a simpler, more reliable test
        let calendar = Calendar.current
        let baseDate = calendar.startOfDay(for: Date())

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Games within a 4-day window (2 teams * 2) with evening times
        // Games must span 4 days for the window to be valid
        let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
        let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: day1Evening
        )
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: day4Evening
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        guard case .success(let options) = result else {
            Issue.record("Expected success with valid team-first request")
            return
        }

        // Verify each route includes games from all 2 teams
        for option in options {
            let allGameIds = Set(option.stops.flatMap { $0.games })

            // Check that at least one home game per team is included
            let hasYankeesGame = allGameIds.contains("yankees-home")
            let hasRedsoxGame = allGameIds.contains("redsox-home")

            #expect(hasYankeesGame, "Every route must include Yankees home game")
            #expect(hasRedsoxGame, "Every route must include Red Sox home game")
        }
    }

    @Test("plan: routes sorted by duration ascending")
    func plan_routesSortedByDurationAscending() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Create multiple windows with different durations
        // Window 1: Games on day 1 and 2 (shorter trip)
        // Window 2: Games on day 10 and 14 (longer trip within window)
        let yankeesGame1 = makeGame(
            id: "yankees-1",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )
        let redsoxGame1 = makeGame(
            id: "redsox-1",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: baseDate.addingTimeInterval(86400 * 2)
        )
        let yankeesGame2 = makeGame(
            id: "yankees-2",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 10)
        )
        let redsoxGame2 = makeGame(
            id: "redsox-2",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: baseDate.addingTimeInterval(86400 * 12)
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame1, redsoxGame1, yankeesGame2, redsoxGame2],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        guard case .success(let options) = result else {
            Issue.record("Expected success")
            return
        }

        // Verify ranking is assigned correctly
        for (index, option) in options.enumerated() {
            #expect(option.rank == index + 1, "Routes should be ranked 1, 2, 3...")
        }
    }

    @Test("plan: respects max driving time constraint")
    func plan_respectsMaxDrivingTimeConstraint() {
        let baseDate = Date()

        // NYC and LA are ~40 hours apart by car
        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let laStadium = makeStadium(id: "la", city: "Los Angeles", coordinate: laCoord)

        // Games on consecutive days - impossible to drive between
        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )
        let dodgersGame = makeGame(
            id: "dodgers-home",
            homeTeamId: "dodgers",
            awayTeamId: "opponent",
            stadiumId: "la",
            dateTime: baseDate.addingTimeInterval(86400 * 2)  // Next day - impossible
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,  // Single driver, 8 hours max
            selectedTeamIds: ["yankees", "dodgers"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, dodgersGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "dodgers": makeTeam(id: "dodgers", name: "Dodgers")
            ],
            stadiums: ["nyc": nycStadium, "la": laStadium]
        )

        let result = planner.plan(request: request)

        // Should fail because driving constraint cannot be met
        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when driving constraint cannot be met")
            return
        }

        // Could be noValidRoutes or constraintsUnsatisfiable
        let validFailures: [PlanningFailure.FailureReason] = [.noValidRoutes, .constraintsUnsatisfiable]
        #expect(validFailures.contains { $0 == failure.reason }, "Should fail due to route constraints")
    }

    // MARK: - E4. Edge Case Tests

    @Test("plan: teams with no overlapping games returns graceful error")
    func plan_noOverlappingGames_returnsGracefulError() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Yankees plays in January, Red Sox plays in July - no overlap
        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate  // Day 0
        )
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: baseDate.addingTimeInterval(86400 * 180)  // 6 months later
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 365),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when teams have no overlapping game windows")
            return
        }

        #expect(failure.reason == .noValidRoutes, "Should return noValidRoutes for non-overlapping schedules")
    }

    @Test("plan: single team selected returns validation error")
    func plan_singleTeamSelected_returnsValidationError() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees"]  // Only 1 team
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame],
            teams: ["yankees": makeTeam(id: "yankees", name: "Yankees")],
            stadiums: ["nyc": nycStadium]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when only 1 team selected")
            return
        }

        #expect(failure.reason == .missingTeamSelection, "Should return missingTeamSelection for single team")
    }

    @Test("plan: no teams selected returns validation error")
    func plan_noTeamsSelected_returnsValidationError() {
        let baseDate = Date()

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: []  // No teams
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [],
            teams: [:],
            stadiums: [:]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when no teams selected")
            return
        }

        #expect(failure.reason == .missingTeamSelection, "Should return missingTeamSelection for no teams")
    }

    @Test("plan: teams in same city treated as separate stops")
    func plan_teamsInSameCity_treatedAsSeparateStops() {
        // Setup: Yankees and Mets both play in NYC but at different stadiums
        let calendar = Calendar.current
        let baseDate = calendar.startOfDay(for: Date())

        let yankeeStadiumCoord = CLLocationCoordinate2D(latitude: 40.8296, longitude: -73.9262)
        let citiFieldCoord = CLLocationCoordinate2D(latitude: 40.7571, longitude: -73.8458)

        let yankeeStadium = makeStadium(id: "yankee-stadium", city: "New York", coordinate: yankeeStadiumCoord)
        let citiField = makeStadium(id: "citi-field", city: "New York", coordinate: citiFieldCoord)

        // Games on different days in the same city with evening times
        // Games must span 4 days for the 2-team window to be valid
        let day1Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 1))!
        let day4Evening = calendar.date(bySettingHour: 19, minute: 0, second: 0, of: baseDate.addingTimeInterval(86400 * 4))!

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "yankee-stadium",
            dateTime: day1Evening
        )
        let metsGame = makeGame(
            id: "mets-home",
            homeTeamId: "mets",
            awayTeamId: "opponent",
            stadiumId: "citi-field",
            dateTime: day4Evening
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "mets"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, metsGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "mets": makeTeam(id: "mets", name: "Mets")
            ],
            stadiums: ["yankee-stadium": yankeeStadium, "citi-field": citiField]
        )

        let result = planner.plan(request: request)

        guard case .success(let options) = result else {
            Issue.record("Expected success with same-city teams")
            return
        }

        #expect(!options.isEmpty, "Should find routes for same-city teams")

        // Verify both games are included in routes
        for option in options {
            let allGameIds = Set(option.stops.flatMap { $0.games })
            #expect(allGameIds.contains("yankees-home"), "Should include Yankees game")
            #expect(allGameIds.contains("mets-home"), "Should include Mets game")
        }
    }

    @Test("plan: team with no home games returns error")
    func plan_teamWithNoHomeGames_returnsError() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)

        // Only Yankees have a home game, Red Sox have no home games
        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "redsox",  // Red Sox are away
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]  // Red Sox selected but no home games
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when selected team has no home games")
            return
        }

        #expect(failure.reason == .noGamesInRange, "Should return noGamesInRange when team has no home games")
    }

    // MARK: - Invariant Tests

    @Test("Invariant: window duration equals teams count times 2")
    func invariant_windowDurationEqualsTeamsTimestwo() {
        // Test that teamFirstMaxDays is calculated correctly
        var prefs = TripPreferences()
        prefs.selectedTeamIds = ["team1", "team2", "team3"]

        #expect(prefs.teamFirstMaxDays == 6, "3 teams should result in 6-day window")

        prefs.selectedTeamIds = ["team1", "team2"]
        #expect(prefs.teamFirstMaxDays == 4, "2 teams should result in 4-day window")

        prefs.selectedTeamIds = ["team1", "team2", "team3", "team4", "team5"]
        #expect(prefs.teamFirstMaxDays == 10, "5 teams should result in 10-day window")
    }

    @Test("Invariant: maximum 10 results returned")
    func invariant_maximum10ResultsReturned() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        // Create many games to generate many possible routes
        var games: [Game] = []
        for day in 0..<60 {
            games.append(makeGame(
                id: "yankees-\(day)",
                homeTeamId: "yankees",
                awayTeamId: "opponent",
                stadiumId: "nyc",
                dateTime: baseDate.addingTimeInterval(Double(86400 * day))
            ))
            games.append(makeGame(
                id: "redsox-\(day)",
                homeTeamId: "redsox",
                awayTeamId: "opponent",
                stadiumId: "boston",
                dateTime: baseDate.addingTimeInterval(Double(86400 * day))
            ))
        }

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 60),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: games,
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            #expect(options.count <= 10, "Should return at most 10 results")
        }
    }

    @Test("Invariant: all routes contain home games from all selected teams")
    func invariant_allRoutesContainAllSelectedTeams() {
        let baseDate = Date()

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)
        let bostonStadium = makeStadium(id: "boston", city: "Boston", coordinate: bostonCoord)

        let yankeesGame = makeGame(
            id: "yankees-home",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: baseDate.addingTimeInterval(86400 * 1)
        )
        let redsoxGame = makeGame(
            id: "redsox-home",
            homeTeamId: "redsox",
            awayTeamId: "opponent",
            stadiumId: "boston",
            dateTime: baseDate.addingTimeInterval(86400 * 2)
        )

        let prefs = TripPreferences(
            planningMode: .teamFirst,
            sports: [.mlb],
            startDate: baseDate,
            endDate: baseDate.addingTimeInterval(86400 * 30),
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            selectedTeamIds: ["yankees", "redsox"]
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [yankeesGame, redsoxGame],
            teams: [
                "yankees": makeTeam(id: "yankees", name: "Yankees"),
                "redsox": makeTeam(id: "redsox", name: "Red Sox")
            ],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            for option in options {
                let allGameIds = Set(option.stops.flatMap { $0.games })

                // At minimum, should have one game per selected team
                let hasYankeesGame = allGameIds.contains { gameId in
                    // Check if any game in this route is a Yankees home game
                    request.availableGames.first { $0.id == gameId }?.homeTeamId == "yankees"
                }
                let hasRedsoxGame = allGameIds.contains { gameId in
                    request.availableGames.first { $0.id == gameId }?.homeTeamId == "redsox"
                }

                #expect(hasYankeesGame, "Every route must include a Yankees home game")
                #expect(hasRedsoxGame, "Every route must include a Red Sox home game")
            }
        }
    }

    // MARK: - Helper Methods

    private func makeStadium(
        id: String,
        city: String,
        coordinate: CLLocationCoordinate2D
    ) -> Stadium {
        Stadium(
            id: id,
            name: "\(city) Stadium",
            city: city,
            state: "XX",
            latitude: coordinate.latitude,
            longitude: coordinate.longitude,
            capacity: 40000,
            sport: .mlb
        )
    }

    private func makeGame(
        id: String,
        homeTeamId: String,
        awayTeamId: String,
        stadiumId: String,
        dateTime: Date
    ) -> Game {
        Game(
            id: id,
            homeTeamId: homeTeamId,
            awayTeamId: awayTeamId,
            stadiumId: stadiumId,
            dateTime: dateTime,
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )
    }

    private func makeTeam(id: String, name: String) -> Team {
        Team(
            id: id,
            name: name,
            abbreviation: String(name.prefix(3)).uppercased(),
            sport: .mlb,
            city: "Test City",
            stadiumId: id,
            primaryColor: "#000000",
            secondaryColor: "#FFFFFF"
        )
    }
}
