//
//  ItineraryBuilder.swift
//  SportsTime
//
//  Shared utility for building itineraries with travel segments.
//  Used by all scenario planners to convert stops into complete itineraries.
//

import Foundation

/// Result of building an itinerary from stops.
struct BuiltItinerary {
    let stops: [ItineraryStop]
    let travelSegments: [TravelSegment]
    let totalDrivingHours: Double
    let totalDistanceMiles: Double
}

/// Shared logic for building itineraries across all scenario planners.
enum ItineraryBuilder {

    /// Validation that can be performed on each travel segment.
    /// Return `true` if the segment is valid, `false` to reject the itinerary.
    typealias SegmentValidator = (TravelSegment, _ fromStop: ItineraryStop, _ toStop: ItineraryStop) -> Bool

    /// Builds a complete itinerary with travel segments between consecutive stops.
    ///
    /// Algorithm:
    ///   1. Handle edge case: single stop = no travel needed
    ///   2. For each consecutive pair of stops, estimate travel
    ///   3. Optionally validate each segment with custom validator
    ///   4. Accumulate driving hours and distance
    ///   5. Verify invariant: travelSegments.count == stops.count - 1
    ///
    /// - Parameters:
    ///   - stops: The stops to connect with travel segments
    ///   - constraints: Driving constraints (drivers, max hours per day)
    ///   - logPrefix: Prefix for log messages (e.g., "[ScenarioA]")
    ///   - segmentValidator: Optional validation for each segment
    ///
    /// - Returns: Built itinerary if successful, nil if any segment fails
    ///
    static func build(
        stops: [ItineraryStop],
        constraints: DrivingConstraints,
        logPrefix: String = "[ItineraryBuilder]",
        segmentValidator: SegmentValidator? = nil
    ) -> BuiltItinerary? {

        // ──────────────────────────────────────────────────────────────────
        // Edge case: Single stop or empty = no travel needed
        // ──────────────────────────────────────────────────────────────────
        if stops.count <= 1 {
            return BuiltItinerary(
                stops: stops,
                travelSegments: [],
                totalDrivingHours: 0,
                totalDistanceMiles: 0
            )
        }

        // ──────────────────────────────────────────────────────────────────
        // Build travel segments between consecutive stops
        // ──────────────────────────────────────────────────────────────────
        var travelSegments: [TravelSegment] = []
        var totalDrivingHours: Double = 0
        var totalDistance: Double = 0

        for index in 0..<(stops.count - 1) {
            let fromStop = stops[index]
            let toStop = stops[index + 1]

            // Estimate travel for this segment
            guard let segment = TravelEstimator.estimate(
                from: fromStop,
                to: toStop,
                constraints: constraints
            ) else {
                return nil
            }

            // Run optional validator (e.g., arrival time check for Scenario B)
            if let validator = segmentValidator {
                if !validator(segment, fromStop, toStop) {
                    return nil
                }
            }

            travelSegments.append(segment)
            totalDrivingHours += segment.estimatedDrivingHours
            totalDistance += segment.estimatedDistanceMiles
        }

        // ──────────────────────────────────────────────────────────────────
        // Verify invariant: segments = stops - 1
        // ──────────────────────────────────────────────────────────────────
        guard travelSegments.count == stops.count - 1 else {
            return nil
        }

        return BuiltItinerary(
            stops: stops,
            travelSegments: travelSegments,
            totalDrivingHours: totalDrivingHours,
            totalDistanceMiles: totalDistance
        )
    }

    // MARK: - EV Charger Enrichment

    /// Enriches an itinerary option with EV charging stops along each route segment.
    ///
    /// This is a post-processing step that can be called after the sync planning completes.
    /// It fetches real routes from MapKit and searches for EV chargers along each segment.
    ///
    /// - Parameter option: The itinerary option to enrich
    /// - Returns: A new itinerary option with EV chargers populated in travel segments
    ///
    static func enrichWithEVChargers(_ option: ItineraryOption) async -> ItineraryOption {
        var enrichedSegments: [TravelSegment] = []

        for segment in option.travelSegments {
            // Get coordinates
            guard let fromCoord = segment.fromLocation.coordinate,
                  let toCoord = segment.toLocation.coordinate else {
                // No coordinates - keep segment as-is
                enrichedSegments.append(segment)
                continue
            }

            do {
                // Fetch real route from MapKit
                let routeInfo = try await LocationService.shared.calculateDrivingRoute(
                    from: fromCoord,
                    to: toCoord
                )

                // Find EV chargers along the route
                var evChargers: [EVChargingStop] = []
                if let polyline = routeInfo.polyline {
                    do {
                        evChargers = try await EVChargingService.shared.findChargersAlongRoute(
                            polyline: polyline,
                            searchRadiusMiles: 5.0,
                            intervalMiles: 100.0
                        )
                    } catch {
                        // EV charger search failed - continue without chargers
                    }
                }

                // Create enriched segment with real route data and EV chargers
                let enrichedSegment = TravelSegment(
                    id: segment.id,
                    fromLocation: segment.fromLocation,
                    toLocation: segment.toLocation,
                    travelMode: segment.travelMode,
                    distanceMeters: routeInfo.distance,
                    durationSeconds: routeInfo.expectedTravelTime,
                    scenicScore: segment.scenicScore,
                    evChargingStops: evChargers
                )
                enrichedSegments.append(enrichedSegment)

            } catch {
                // Route calculation failed - keep original segment
                enrichedSegments.append(segment)
            }
        }

        // Recalculate totals with enriched segments
        let totalDrivingHours = enrichedSegments.reduce(0.0) { $0 + $1.estimatedDrivingHours }
        let totalDistanceMiles = enrichedSegments.reduce(0.0) { $0 + $1.estimatedDistanceMiles }

        return ItineraryOption(
            rank: option.rank,
            stops: option.stops,
            travelSegments: enrichedSegments,
            totalDrivingHours: totalDrivingHours,
            totalDistanceMiles: totalDistanceMiles,
            geographicRationale: option.geographicRationale
        )
    }

    /// Enriches multiple itinerary options with EV chargers.
    /// Processes in parallel for better performance.
    static func enrichWithEVChargers(_ options: [ItineraryOption]) async -> [ItineraryOption] {
        await withTaskGroup(of: (Int, ItineraryOption).self) { group in
            for (index, option) in options.enumerated() {
                group.addTask {
                    let enriched = await enrichWithEVChargers(option)
                    return (index, enriched)
                }
            }

            var results = [(Int, ItineraryOption)]()
            for await result in group {
                results.append(result)
            }

            // Sort by original index to maintain order
            return results.sorted { $0.0 < $1.0 }.map { $0.1 }
        }
    }

    // MARK: - Async Build with EV Chargers

    /// Builds an itinerary with real route data and optional EV charger discovery.
    ///
    /// This async version uses MapKit to get actual driving routes (instead of Haversine estimates)
    /// and can optionally search for EV charging stations along each route segment.
    ///
    /// - Parameters:
    ///   - stops: The stops to connect with travel segments
    ///   - constraints: Driving constraints (drivers, max hours per day)
    ///   - includeEVChargers: Whether to search for EV chargers along routes
    ///   - logPrefix: Prefix for log messages
    ///   - segmentValidator: Optional validation for each segment
    ///
    /// - Returns: Built itinerary if successful, nil if any segment fails
    ///
    static func buildAsync(
        stops: [ItineraryStop],
        constraints: DrivingConstraints,
        includeEVChargers: Bool = false,
        logPrefix: String = "[ItineraryBuilder]",
        segmentValidator: SegmentValidator? = nil
    ) async -> BuiltItinerary? {

        // Edge case: Single stop or empty = no travel needed
        if stops.count <= 1 {
            return BuiltItinerary(
                stops: stops,
                travelSegments: [],
                totalDrivingHours: 0,
                totalDistanceMiles: 0
            )
        }

        var travelSegments: [TravelSegment] = []
        var totalDrivingHours: Double = 0
        var totalDistance: Double = 0

        for index in 0..<(stops.count - 1) {
            let fromStop = stops[index]
            let toStop = stops[index + 1]

            // Get coordinates
            guard let fromCoord = fromStop.coordinate,
                  let toCoord = toStop.coordinate else {
                // Fall back to estimate
                guard let segment = TravelEstimator.estimate(
                    from: fromStop,
                    to: toStop,
                    constraints: constraints
                ) else {
                    return nil
                }
                travelSegments.append(segment)
                totalDrivingHours += segment.estimatedDrivingHours
                totalDistance += segment.estimatedDistanceMiles
                continue
            }

            // Fetch real route from MapKit
            do {
                let routeInfo = try await LocationService.shared.calculateDrivingRoute(
                    from: fromCoord,
                    to: toCoord
                )

                // Find EV chargers if requested and polyline is available
                var evChargers: [EVChargingStop] = []
                if includeEVChargers, let polyline = routeInfo.polyline {
                    do {
                        evChargers = try await EVChargingService.shared.findChargersAlongRoute(
                            polyline: polyline,
                            searchRadiusMiles: 5.0,
                            intervalMiles: 100.0
                        )
                    } catch {
                        // Continue without chargers - not a critical failure
                    }
                }

                let segment = TravelSegment(
                    fromLocation: fromStop.location,
                    toLocation: toStop.location,
                    travelMode: .drive,
                    distanceMeters: routeInfo.distance,
                    durationSeconds: routeInfo.expectedTravelTime,
                    evChargingStops: evChargers
                )

                // Run optional validator
                if let validator = segmentValidator {
                    if !validator(segment, fromStop, toStop) {
                        return nil
                    }
                }

                travelSegments.append(segment)
                totalDrivingHours += segment.estimatedDrivingHours
                totalDistance += segment.estimatedDistanceMiles

            } catch {
                // Fall back to estimate
                guard let segment = TravelEstimator.estimate(
                    from: fromStop,
                    to: toStop,
                    constraints: constraints
                ) else {
                    return nil
                }
                travelSegments.append(segment)
                totalDrivingHours += segment.estimatedDrivingHours
                totalDistance += segment.estimatedDistanceMiles
            }
        }

        // Verify invariant
        guard travelSegments.count == stops.count - 1 else {
            return nil
        }

        return BuiltItinerary(
            stops: stops,
            travelSegments: travelSegments,
            totalDrivingHours: totalDrivingHours,
            totalDistanceMiles: totalDistance
        )
    }

    // MARK: - Common Validators

    /// Validator that ensures travel duration allows arrival before game start.
    /// Used by Scenario B where selected games have fixed start times.
    ///
    /// This checks if the travel duration is short enough that the user could
    /// theoretically leave after the previous game and arrive before the next.
    ///
    /// - Parameter bufferSeconds: Time buffer before game start (default 1 hour)
    /// - Returns: Validator closure
    ///
    static func arrivalBeforeGameStart(bufferSeconds: TimeInterval = 3600) -> SegmentValidator {
        return { segment, fromStop, toStop in
            guard let gameStart = toStop.firstGameStart else {
                return true  // No game = no constraint
            }

            // Check if there's enough time between departure point and game start
            // Departure assumed after previous day's activities (use departure date as baseline)
            let earliestDeparture = fromStop.departureDate
            let travelDuration = segment.durationSeconds
            let earliestArrival = earliestDeparture.addingTimeInterval(travelDuration)
            let deadline = gameStart.addingTimeInterval(-bufferSeconds)

            if earliestArrival > deadline {
                return false
            }
            return true
        }
    }
}
