//
//  ScenarioAPlanner.swift
//  SportsTime
//
//  Scenario A: Date range only planning.
//  User provides a date range, we find all games and build chronological routes.
//

import Foundation
import CoreLocation

/// Scenario A: Date range planning
///
/// This is the simplest scenario - user just picks a date range and we find games.
///
/// Input:
///   - date_range: Required. The trip dates (e.g., Jan 5-15)
///   - must_stop: Optional. A location they must visit (not yet implemented)
///
/// Output:
///   - Success: Ranked list of itinerary options
///   - Failure: Explicit error with reason (no games, dates invalid, etc.)
///
/// Example:
///   User selects Jan 5-10, 2026
///   We find: Lakers (Jan 5), Warriors (Jan 7), Kings (Jan 9)
///   Output: Single itinerary visiting LA → SF → Sacramento in order
///
final class ScenarioAPlanner: ScenarioPlanner {

    // MARK: - ScenarioPlanner Protocol

    /// Main entry point for Scenario A planning.
    ///
    /// Flow:
    ///   1. Validate inputs (date range must exist)
    ///   2. Find all games within the date range
    ///   3. Convert games to stops (grouping by stadium)
    ///   4. Calculate travel between stops
    ///   5. Return the complete itinerary
    ///
    /// Failure cases:
    ///   - No date range provided → .missingDateRange
    ///   - No games in date range → .noGamesInRange
    ///   - Can't build valid route → .constraintsUnsatisfiable
    ///
    func plan(request: PlanningRequest) -> ItineraryResult {

        // ──────────────────────────────────────────────────────────────────
        // Step 1: Validate date range exists
        // ──────────────────────────────────────────────────────────────────
        // Scenario A requires a date range. Without it, we can't filter games.
        guard let dateRange = request.dateRange else {
            return .failure(
                PlanningFailure(
                    reason: .missingDateRange,
                    violations: []
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 2: Filter games within date range and selected regions
        // ──────────────────────────────────────────────────────────────────
        // Get all games that fall within the user's travel dates.
        // Sort by start time so we visit them in chronological order.
        let selectedRegions = request.preferences.selectedRegions
        let gamesInRange = request.allGames
            .filter { game in
                // Must be in date range
                guard dateRange.contains(game.startTime) else { 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)
                    return selectedRegions.contains(gameRegion)
                }
                return true
            }
            .sorted { $0.startTime < $1.startTime }

        // No games? Nothing to plan.
        if gamesInRange.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .noGamesInRange,
                    violations: []
                )
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 3: Find ALL geographically sensible route variations
        // ──────────────────────────────────────────────────────────────────
        // Not all games in the date range may form a sensible route.
        // Example: NY (Jan 5), TX (Jan 6), SC (Jan 7), CA (Jan 8)
        //   - Including all = zig-zag nightmare
        //   - Option 1: NY, TX, DEN, NM, CA (skip SC)
        //   - Option 2: NY, SC, DEN, NM, CA (skip TX)
        //   - etc.
        //
        // We explore ALL valid combinations and return multiple options.
        // Uses GameDAGRouter for polynomial-time beam search.
        //
        // Run beam search BOTH globally AND per-region to get diverse routes:
        // - Global search finds cross-region routes
        // - Per-region search ensures we have good regional options too
        // Travel style filtering happens at UI layer.
        //
        var validRoutes: [[Game]] = []

        // Global beam search (finds cross-region routes)
        let globalRoutes = GameDAGRouter.findAllSensibleRoutes(
            from: gamesInRange,
            stadiums: request.stadiums,
            allowRepeatCities: request.preferences.allowRepeatCities,
            stopBuilder: buildStops
        )
        validRoutes.append(contentsOf: globalRoutes)

        // Per-region beam search (ensures good regional options)
        let regionalRoutes = findRoutesPerRegion(
            games: gamesInRange,
            stadiums: request.stadiums,
            allowRepeatCities: request.preferences.allowRepeatCities
        )
        validRoutes.append(contentsOf: regionalRoutes)

        // Deduplicate routes (same game IDs)
        validRoutes = deduplicateRoutes(validRoutes)

        print("🔍 ScenarioA: gamesInRange=\(gamesInRange.count), validRoutes=\(validRoutes.count)")
        if let firstRoute = validRoutes.first {
            print("🔍 ScenarioA: First route has \(firstRoute.count) games")
            let cities = firstRoute.compactMap { request.stadiums[$0.stadiumId]?.city }
            print("🔍 ScenarioA: Route cities: \(cities)")
        }

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

        // ──────────────────────────────────────────────────────────────────
        // Step 4: Build itineraries for each valid route
        // ──────────────────────────────────────────────────────────────────
        // For each valid game combination, build stops and calculate travel.
        // Some routes may fail driving constraints - filter those out.
        //
        var itineraryOptions: [ItineraryOption] = []

        var routesAttempted = 0
        var routesFailed = 0

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

            // Debug: show stops created from games
            let stopCities = stops.map { "\($0.city)(\($0.games.count)g)" }
            print("🔍 ScenarioA: Route \(index) - \(routeGames.count) games → \(stops.count) stops: \(stopCities)")

            guard !stops.isEmpty else {
                routesFailed += 1
                print("⚠️ ScenarioA: Route \(index) - buildStops returned empty")
                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
                routesFailed += 1
                print("⚠️ ScenarioA: Route \(index) - ItineraryBuilder.build failed")
                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: "\(stops.count) games: \(cities)"
            )
            itineraryOptions.append(option)
        }

        // ──────────────────────────────────────────────────────────────────
        // Step 5: Return ranked results
        // ──────────────────────────────────────────────────────────────────
        // If no routes passed all constraints, fail.
        // Otherwise, return all valid options for the user to choose from.
        //
        if itineraryOptions.isEmpty {
            return .failure(
                PlanningFailure(
                    reason: .constraintsUnsatisfiable,
                    violations: [
                        ConstraintViolation(
                            type: .drivingTime,
                            description: "No routes satisfy driving constraints",
                            severity: .error
                        )
                    ]
                )
            )
        }

        // Sort and rank based on leisure level
        let leisureLevel = request.preferences.leisureLevel

        // Debug: show all options before sorting
        print("🔍 ScenarioA: \(itineraryOptions.count) itinerary options before sorting:")
        for (i, opt) in itineraryOptions.enumerated() {
            print("   Option \(i): \(opt.stops.count) stops, \(opt.totalGames) games, \(String(format: "%.1f", opt.totalDrivingHours))h driving")
        }

        let rankedOptions = ItineraryOption.sortByLeisure(
            itineraryOptions,
            leisureLevel: leisureLevel
        )

        print("🔍 ScenarioA: Returning \(rankedOptions.count) options after sorting")
        if let first = rankedOptions.first {
            print("🔍 ScenarioA: First option has \(first.stops.count) stops")
        }

        return .success(rankedOptions)
    }

    // MARK: - Stop Building

    /// Converts a list of games into itinerary stops.
    ///
    /// The goal: Create one stop per stadium, in the order we first encounter each stadium
    /// when walking through games chronologically.
    ///
    /// Example:
    ///   Input games (already sorted by date):
    ///     1. Jan 5 - Lakers @ Staples Center (LA)
    ///     2. Jan 6 - Clippers @ Staples Center (LA)  <- same stadium as #1
    ///     3. Jan 8 - Warriors @ Chase Center (SF)
    ///
    ///   Output stops:
    ///     Stop 1: Los Angeles (contains game 1 and 2)
    ///     Stop 2: San Francisco (contains game 3)
    ///
    /// Note: If you visit the same city, leave, and come back, that creates
    /// separate stops (one for each visit).
    ///
    private func buildStops(
        from games: [Game],
        stadiums: [UUID: Stadium]
    ) -> [ItineraryStop] {
        guard !games.isEmpty else { return [] }

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

        // Group consecutive games at the same stadium into stops
        // If you visit A, then B, then A again, that's 3 stops (A, B, A)
        var stops: [ItineraryStop] = []
        var currentStadiumId: UUID? = nil
        var currentGames: [Game] = []

        for game in sortedGames {
            if game.stadiumId == currentStadiumId {
                // Same stadium as previous game - add to current group
                currentGames.append(game)
            } else {
                // Different stadium - finalize previous stop (if any) and start new one
                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: UUID,
        stadiums: [UUID: 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
        )

        // departureDate is day AFTER last game (we leave the next morning)
        let lastGameDate = sortedGames.last?.gameDate ?? Date()
        let departureDateValue = Calendar.current.date(byAdding: .day, value: 1, to: lastGameDate) ?? lastGameDate

        return ItineraryStop(
            city: city,
            state: state,
            coordinate: coordinate,
            games: sortedGames.map { $0.id },
            arrivalDate: sortedGames.first?.gameDate ?? Date(),
            departureDate: departureDateValue,
            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.uuidString }.sorted().joined(separator: "-")
            if !seen.contains(key) {
                seen.insert(key)
                unique.append(route)
            }
        }

        return unique
    }

    // MARK: - Regional Route Finding

    /// Finds routes by running beam search separately for each geographic region.
    /// This ensures we get diverse options from East, Central, and West coasts.
    private func findRoutesPerRegion(
        games: [Game],
        stadiums: [UUID: Stadium],
        allowRepeatCities: Bool
    ) -> [[Game]] {
        // Partition games by region
        var gamesByRegion: [Region: [Game]] = [:]

        for game in games {
            guard let stadium = stadiums[game.stadiumId] else { continue }
            let coord = stadium.coordinate
            let region = Region.classify(longitude: coord.longitude)
            // Only consider actual regions, not cross-country
            if region != .crossCountry {
                gamesByRegion[region, default: []].append(game)
            }
        }

        print("🔍 ScenarioA Regional: Partitioned \(games.count) games into \(gamesByRegion.count) regions")
        for (region, regionGames) in gamesByRegion {
            print("   \(region.shortName): \(regionGames.count) games")
        }

        // Run beam search for each region
        var allRoutes: [[Game]] = []

        for (region, regionGames) in gamesByRegion {
            guard !regionGames.isEmpty else { continue }

            let regionRoutes = GameDAGRouter.findAllSensibleRoutes(
                from: regionGames,
                stadiums: stadiums,
                allowRepeatCities: allowRepeatCities,
                stopBuilder: buildStops
            )

            print("🔍 ScenarioA Regional: \(region.shortName) produced \(regionRoutes.count) routes")
            allRoutes.append(contentsOf: regionRoutes)
        }

        return allRoutes
    }

}
