//
//  ScenarioDPlanner.swift
//  SportsTime
//
//  Scenario D: Follow Team planning.
//  User selects a team, we find all their games (home and away) and build routes.
//

import Foundation
import CoreLocation

/// Scenario D: Follow Team planning
///
/// This scenario builds trips around a specific team's schedule.
///
/// Input:
///   - followTeamId: Required. The team to follow.
///   - date_range: Required. The trip dates.
///   - selectedRegions: Optional. Filter to specific regions.
///   - useHomeLocation: Whether to start/end from user's home.
///   - startLocation: Required if useHomeLocation is true.
///
/// Output:
///   - Success: Ranked list of itinerary options
///   - Failure: Explicit error with reason (no team, no games, etc.)
///
/// Example:
///   User follows Yankees, Jan 5-15, 2026
///   We find: @Red Sox (Jan 5), @Blue Jays (Jan 8), vs Orioles (Jan 12)
///   Output: Route visiting Boston → Toronto → New York
///
final class ScenarioDPlanner: ScenarioPlanner {

    // MARK: - ScenarioPlanner Protocol

    /// Main entry point for Scenario D planning.
    ///
    /// Flow:
    ///   1. Validate inputs (team must be selected)
    ///   2. Filter games to team's schedule (home and away)
    ///   3. Apply region and date filters
    ///   4. Apply repeat city constraints
    ///   5. Build routes and calculate travel
    ///   6. Return ranked itineraries
    ///
    /// Failure cases:
    ///   - No team selected → .missingTeamSelection
    ///   - No date range → .missingDateRange
    ///   - No games found → .noGamesInRange
    ///   - Can't build valid route → .constraintsUnsatisfiable
    ///
    func plan(request: PlanningRequest) -> ItineraryResult {

        // ──────────────────────────────────────────────────────────────────
        // Step 1: Validate team selection
        // ──────────────────────────────────────────────────────────────────
        guard let teamId = request.preferences.followTeamId else {
            return .failure(
                PlanningFailure(
                    reason: .missingTeamSelection,
                    violations: []
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 2: Validate date range exists
        // ──────────────────────────────────────────────────────────────────
        guard let dateRange = request.dateRange else {
            return .failure(
                PlanningFailure(
                    reason: .missingDateRange,
                    violations: []
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 3: Filter games to team's schedule (home and away)
        // ──────────────────────────────────────────────────────────────────
        let teamGames = filterToTeam(request.allGames, teamId: teamId)

        print("🔍 ScenarioD Step 3: allGames=\(request.allGames.count), teamGames=\(teamGames.count)")
        print("🔍 ScenarioD: Looking for teamId=\(teamId)")
        for game in teamGames.prefix(20) {
            let stadium = request.stadiums[game.stadiumId]
            let isHome = game.homeTeamId == teamId
            print("🔍   Game: \(stadium?.city ?? "?") on \(game.gameDate) (\(isHome ? "HOME" : "AWAY"))")
        }

        if teamGames.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .noGamesInRange,
                    violations: [
                        ConstraintViolation(
                            type: .selectedGames,
                            description: "No games found for selected team",
                            severity: .error
                        )
                    ]
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 4: Apply date range and region filters
        // ──────────────────────────────────────────────────────────────────
        let selectedRegions = request.preferences.selectedRegions
        print("🔍 ScenarioD Step 4: dateRange=\(dateRange.start) to \(dateRange.end), selectedRegions=\(selectedRegions)")

        let filteredGames = teamGames
            .filter { game in
                // Must be in date range
                let inDateRange = dateRange.contains(game.startTime)
                if !inDateRange {
                    let stadium = request.stadiums[game.stadiumId]
                    print("🔍   FILTERED OUT (date): \(stadium?.city ?? "?") on \(game.gameDate) - startTime=\(game.startTime)")
                    return false
                }

                // Must be in selected region (if regions specified)
                if !selectedRegions.isEmpty {
                    guard let stadium = request.stadiums[game.stadiumId] else { return false }
                    let gameRegion = Region.classify(longitude: stadium.coordinate.longitude)
                    let inRegion = selectedRegions.contains(gameRegion)
                    if !inRegion {
                        print("🔍   FILTERED OUT (region): \(stadium.city) on \(game.gameDate) - region=\(gameRegion)")
                    }
                    return inRegion
                }
                return true
            }
            .sorted { $0.startTime < $1.startTime }

        print("🔍 ScenarioD Step 4 result: \(filteredGames.count) games after date/region filter")
        for game in filteredGames {
            let stadium = request.stadiums[game.stadiumId]
            print("🔍   Kept: \(stadium?.city ?? "?") on \(game.gameDate)")
        }

        if filteredGames.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .noGamesInRange,
                    violations: [
                        ConstraintViolation(
                            type: .dateRange,
                            description: "No team games found in selected date range and regions",
                            severity: .error
                        )
                    ]
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 5: Prepare for routing
        // ──────────────────────────────────────────────────────────────────
        // NOTE: We do NOT filter by repeat city here. The GameDAGRouter handles
        // allowRepeatCities internally, which allows it to pick the optimal game
        // per city for route feasibility (e.g., pick July 29 Anaheim instead of
        // July 27 if it makes the driving from Chicago feasible).
        let finalGames = filteredGames

        print("🔍 ScenarioD Step 5: Passing \(finalGames.count) games to router (allowRepeatCities=\(request.preferences.allowRepeatCities))")
        print("🔍 ScenarioD: teamGames=\(teamGames.count), filteredGames=\(filteredGames.count), finalGames=\(finalGames.count)")

        // ──────────────────────────────────────────────────────────────────
        // Step 6: Find valid routes using DAG router
        // ──────────────────────────────────────────────────────────────────
        // Follow Team mode typically has fewer games than Scenario A,
        // so we can be more exhaustive in route finding.
        print("🔍 ScenarioD Step 6: Finding routes from \(finalGames.count) games")

        var validRoutes: [[Game]] = []

        let globalRoutes = GameDAGRouter.findAllSensibleRoutes(
            from: finalGames,
            stadiums: request.stadiums,
            allowRepeatCities: request.preferences.allowRepeatCities,
            stopBuilder: buildStops
        )
        print("🔍 ScenarioD Step 6: GameDAGRouter returned \(globalRoutes.count) routes")
        for (i, route) in globalRoutes.prefix(5).enumerated() {
            let cities = route.compactMap { request.stadiums[$0.stadiumId]?.city }.joined(separator: " → ")
            print("🔍   Route \(i+1): \(route.count) games - \(cities)")
        }

        validRoutes.append(contentsOf: globalRoutes)

        // Deduplicate routes
        validRoutes = deduplicateRoutes(validRoutes)

        print("🔍 ScenarioD Step 6 after dedup: \(validRoutes.count) valid routes")

        if validRoutes.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .noValidRoutes,
                    violations: [
                        ConstraintViolation(
                            type: .geographicSanity,
                            description: "No geographically sensible route found for team's games",
                            severity: .error
                        )
                    ]
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 7: Build itineraries for each valid route
        // ──────────────────────────────────────────────────────────────────
        var itineraryOptions: [ItineraryOption] = []

        for (index, routeGames) in validRoutes.enumerated() {
            // Build stops for this route
            let stops = buildStops(from: routeGames, stadiums: request.stadiums)

            guard !stops.isEmpty else { continue }

            // Calculate travel segments using shared ItineraryBuilder
            guard let itinerary = ItineraryBuilder.build(
                stops: stops,
                constraints: request.drivingConstraints
            ) else {
                // This route fails driving constraints, skip it
                continue
            }

            // Create the option
            let cities = stops.map { $0.city }.joined(separator: " → ")
            let option = ItineraryOption(
                rank: index + 1,
                stops: itinerary.stops,
                travelSegments: itinerary.travelSegments,
                totalDrivingHours: itinerary.totalDrivingHours,
                totalDistanceMiles: itinerary.totalDistanceMiles,
                geographicRationale: "Follow Team: \(stops.count) games - \(cities)"
            )
            itineraryOptions.append(option)
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 8: Return ranked results
        // ──────────────────────────────────────────────────────────────────
        if itineraryOptions.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .constraintsUnsatisfiable,
                    violations: [
                        ConstraintViolation(
                            type: .drivingTime,
                            description: "No routes satisfy driving constraints for team's schedule",
                            severity: .error
                        )
                    ]
                )
            )
        }

        // Sort and rank based on leisure level
        let leisureLevel = request.preferences.leisureLevel
        let rankedOptions = ItineraryOption.sortByLeisure(
            itineraryOptions,
            leisureLevel: leisureLevel
        )

        print("🔍 ScenarioD: Returning \(rankedOptions.count) options")

        return .success(rankedOptions)
    }

    // MARK: - Team Filtering

    /// Filters games to those involving the followed team (home or away).
    private func filterToTeam(_ games: [Game], teamId: String) -> [Game] {
        games.filter { game in
            game.homeTeamId == teamId || game.awayTeamId == teamId
        }
    }

    // MARK: - Repeat City Filtering

    /// When `allowRepeatCities = false`, keeps only the first game per city.
    private func applyRepeatCityFilter(
        _ games: [Game],
        allowRepeat: Bool,
        stadiums: [String: Stadium]
    ) -> [Game] {
        guard !allowRepeat else {
            print("🔍   applyRepeatCityFilter: allowRepeat=true, returning all \(games.count) games")
            return games
        }

        print("🔍   applyRepeatCityFilter: allowRepeat=false, filtering duplicates")
        var seenCities: Set<String> = []
        return games.filter { game in
            guard let stadium = stadiums[game.stadiumId] else {
                print("🔍     Game \(game.id): NO STADIUM FOUND - filtered out")
                return false
            }
            if seenCities.contains(stadium.city) {
                print("🔍     Game in \(stadium.city) on \(game.gameDate): DUPLICATE CITY - filtered out")
                return false
            }
            print("🔍     Game in \(stadium.city) on \(game.gameDate): KEPT (first in this city)")
            seenCities.insert(stadium.city)
            return true
        }
    }

    // MARK: - Stop Building

    /// Converts a list of games into itinerary stops.
    /// Same logic as ScenarioAPlanner.
    private func buildStops(
        from games: [Game],
        stadiums: [String: Stadium]
    ) -> [ItineraryStop] {
        guard !games.isEmpty else { return [] }

        let sortedGames = games.sorted { $0.startTime < $1.startTime }

        var stops: [ItineraryStop] = []
        var currentStadiumId: String? = nil
        var currentGames: [Game] = []

        for game in sortedGames {
            if game.stadiumId == currentStadiumId {
                currentGames.append(game)
            } else {
                if let stadiumId = currentStadiumId, !currentGames.isEmpty {
                    if let stop = createStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums) {
                        stops.append(stop)
                    }
                }
                currentStadiumId = game.stadiumId
                currentGames = [game]
            }
        }

        // Don't forget the last group
        if let stadiumId = currentStadiumId, !currentGames.isEmpty {
            if let stop = createStop(from: currentGames, stadiumId: stadiumId, stadiums: stadiums) {
                stops.append(stop)
            }
        }

        return stops
    }

    /// Creates an ItineraryStop from a group of games at the same stadium.
    private func createStop(
        from games: [Game],
        stadiumId: String,
        stadiums: [String: Stadium]
    ) -> ItineraryStop? {
        guard !games.isEmpty else { return nil }

        let sortedGames = games.sorted { $0.startTime < $1.startTime }
        let stadium = stadiums[stadiumId]
        let city = stadium?.city ?? "Unknown"
        let state = stadium?.state ?? ""
        let coordinate = stadium?.coordinate

        let location = LocationInput(
            name: city,
            coordinate: coordinate,
            address: stadium?.fullAddress
        )

        let lastGameDate = sortedGames.last?.gameDate ?? Date()

        return ItineraryStop(
            city: city,
            state: state,
            coordinate: coordinate,
            games: sortedGames.map { $0.id },
            arrivalDate: sortedGames.first?.gameDate ?? Date(),
            departureDate: lastGameDate,
            location: location,
            firstGameStart: sortedGames.first?.startTime
        )
    }

    // MARK: - Route Deduplication

    /// Removes duplicate routes (routes with identical game IDs).
    private func deduplicateRoutes(_ routes: [[Game]]) -> [[Game]] {
        var seen = Set<String>()
        var unique: [[Game]] = []

        for route in routes {
            let key = route.map { $0.id }.sorted().joined(separator: "-")
            if !seen.contains(key) {
                seen.insert(key)
                unique.append(route)
            }
        }

        return unique
    }
}
