//
//  ScenarioDPlannerTests.swift
//  SportsTimeTests
//
//  TDD specification tests for ScenarioDPlanner.
//

import Testing
import CoreLocation
@testable import SportsTime

@Suite("ScenarioDPlanner")
struct ScenarioDPlannerTests {

    // MARK: - Test Data

    private let planner = ScenarioDPlanner()

    private let nycCoord = CLLocationCoordinate2D(latitude: 40.7580, longitude: -73.9855)
    private let bostonCoord = CLLocationCoordinate2D(latitude: 42.3467, longitude: -71.0972)
    private let chicagoCoord = CLLocationCoordinate2D(latitude: 41.8827, longitude: -87.6233)

    // MARK: - Specification Tests: Missing Team

    @Test("plan: no followTeamId returns missingTeamSelection failure")
    func plan_noFollowTeamId_returnsMissingTeamSelection() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 7)

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: nil  // Missing
        )

        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 followTeamId missing")
            return
        }
        #expect(failure.reason == .missingTeamSelection)
    }

    // MARK: - Specification Tests: No Team Games

    @Test("plan: no games for team returns noGamesInRange failure")
    func plan_noGamesForTeam_returnsNoGamesInRange() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 7)

        let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
        // Game is for different team
        let game = Game(
            id: "game1",
            homeTeamId: "other-team",
            awayTeamId: "another-team",
            stadiumId: "stadium1",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: "yankees"  // Team not in any game
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [game],
            teams: [:],
            stadiums: ["stadium1": stadium]
        )

        let result = planner.plan(request: request)

        guard case .failure(let failure) = result else {
            Issue.record("Expected failure when team has no games")
            return
        }
        #expect(failure.reason == .noGamesInRange)
    }

    // MARK: - Specification Tests: Home and Away Games

    @Test("plan: includes both home and away games for team")
    func plan_includesBothHomeAndAwayGames() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 14)

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

        // Home game: Yankees at home
        let homeGame = Game(
            id: "home-game",
            homeTeamId: "yankees",
            awayTeamId: "red-sox",
            stadiumId: "nyc",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        // Away game: Yankees away
        let awayGame = Game(
            id: "away-game",
            homeTeamId: "red-sox",
            awayTeamId: "yankees",
            stadiumId: "boston",
            dateTime: startDate.addingTimeInterval(86400 * 5),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            followTeamId: "yankees"
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [homeGame, awayGame],
            teams: [:],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
            // Both home and away games should be includable
            let hasHomeGame = allGameIds.contains("home-game")
            let hasAwayGame = allGameIds.contains("away-game")
            #expect(hasHomeGame || hasAwayGame, "Should include at least one team game")
        }
    }

    // MARK: - Specification Tests: Region Filtering

    @Test("plan: with selectedRegions filters team games to those regions")
    func plan_withSelectedRegions_filtersTeamGames() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 14)

        let nycStadium = makeStadium(id: "nyc", city: "New York", coordinate: nycCoord)  // East
        let chicagoStadium = makeStadium(id: "chicago", city: "Chicago", coordinate: chicagoCoord)  // Central

        // East coast game
        let eastGame = Game(
            id: "east-game",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        // Central game
        let centralGame = Game(
            id: "central-game",
            homeTeamId: "cubs",
            awayTeamId: "yankees",
            stadiumId: "chicago",
            dateTime: startDate.addingTimeInterval(86400 * 5),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        var prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: "yankees"
        )
        prefs.selectedRegions = [.east]  // Only East coast

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [eastGame, centralGame],
            teams: [:],
            stadiums: ["nyc": nycStadium, "chicago": chicagoStadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
            #expect(!allGameIds.contains("central-game"), "Central game should be filtered by East region")
        }
    }

    // MARK: - Specification Tests: Successful Planning

    @Test("plan: valid request returns success")
    func plan_validRequest_returnsSuccess() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 7)

        let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
        let game = Game(
            id: "game1",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "stadium1",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: "yankees"
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [game],
            teams: [:],
            stadiums: ["stadium1": stadium]
        )

        let result = planner.plan(request: request)

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

        #expect(!options.isEmpty)
    }

    // MARK: - Invariant Tests

    @Test("Invariant: all returned games have team as home or away")
    func invariant_allGamesHaveTeam() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 14)

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

        let teamId = "yankees"

        // Games involving the team
        let homeGame = Game(
            id: "home",
            homeTeamId: teamId,
            awayTeamId: "opponent",
            stadiumId: "nyc",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let awayGame = Game(
            id: "away",
            homeTeamId: "red-sox",
            awayTeamId: teamId,
            stadiumId: "boston",
            dateTime: startDate.addingTimeInterval(86400 * 5),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        // Game NOT involving the team
        let otherGame = Game(
            id: "other",
            homeTeamId: "mets",
            awayTeamId: "phillies",
            stadiumId: "nyc",
            dateTime: startDate.addingTimeInterval(86400 * 3),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 2,
            followTeamId: teamId
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [homeGame, awayGame, otherGame],
            teams: [:],
            stadiums: ["nyc": nycStadium, "boston": bostonStadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            let allGameIds = options.flatMap { $0.stops.flatMap { $0.games } }
            #expect(!allGameIds.contains("other"), "Games not involving the followed team should be excluded")
        }
    }

    @Test("Invariant: duplicate routes are removed")
    func invariant_duplicateRoutesRemoved() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 7)

        let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
        let game = Game(
            id: "game1",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "stadium1",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: "yankees"
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [game],
            teams: [:],
            stadiums: ["stadium1": stadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            // Verify no duplicate game combinations
            var seenGameCombinations = Set<String>()
            for option in options {
                let gameIds = option.stops.flatMap { $0.games }.sorted().joined(separator: "-")
                #expect(!seenGameCombinations.contains(gameIds), "Duplicate route found: \(gameIds)")
                seenGameCombinations.insert(gameIds)
            }
        }
    }

    // MARK: - Property Tests

    @Test("Property: success always has non-empty options")
    func property_successHasNonEmptyOptions() {
        let startDate = Date()
        let endDate = startDate.addingTimeInterval(86400 * 7)

        let stadium = makeStadium(id: "stadium1", city: "New York", coordinate: nycCoord)
        let game = Game(
            id: "game1",
            homeTeamId: "yankees",
            awayTeamId: "opponent",
            stadiumId: "stadium1",
            dateTime: startDate.addingTimeInterval(86400 * 2),
            sport: .mlb,
            season: "2026",
            isPlayoff: false
        )

        let prefs = TripPreferences(
            planningMode: .followTeam,
            sports: [.mlb],
            startDate: startDate,
            endDate: endDate,
            leisureLevel: .moderate,
            lodgingType: .hotel,
            numberOfDrivers: 1,
            followTeamId: "yankees"
        )

        let request = PlanningRequest(
            preferences: prefs,
            availableGames: [game],
            teams: [:],
            stadiums: ["stadium1": stadium]
        )

        let result = planner.plan(request: request)

        if case .success(let options) = result {
            #expect(!options.isEmpty, "Success must have at least one option")
            for option in options {
                #expect(!option.stops.isEmpty, "Each option must have stops")
            }
        }
    }

    // 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
        )
    }
}
