//
//  TravelEstimator.swift
//  SportsTime
//
//  Shared travel estimation logic used by all scenario planners.
//  Estimating travel from A to B is the same regardless of planning scenario.
//

import Foundation
import CoreLocation

enum TravelEstimator {

    // MARK: - Constants

    private static let averageSpeedMph: Double = 60.0
    private static let roadRoutingFactor: Double = 1.3 // Straight line to road distance
    private static let fallbackDistanceMiles: Double = 300.0

    // MARK: - Travel Estimation

    /// Estimates a travel segment between two stops.
    /// Returns nil if trip exceeds maximum allowed driving hours (2 days worth).
    static func estimate(
        from: ItineraryStop,
        to: ItineraryStop,
        constraints: DrivingConstraints
    ) -> TravelSegment? {

        let distanceMiles = calculateDistanceMiles(from: from, to: to)
        let drivingHours = distanceMiles / averageSpeedMph

        // Maximum allowed: 5 days of driving (matches GameDAGRouter.maxDayLookahead)
        // This allows multi-day cross-country segments like Chicago → Anaheim
        let maxAllowedHours = constraints.maxDailyDrivingHours * 5.0
        if drivingHours > maxAllowedHours {
            return nil
        }

        return TravelSegment(
            fromLocation: from.location,
            toLocation: to.location,
            travelMode: .drive,
            distanceMeters: distanceMiles * 1609.34,
            durationSeconds: drivingHours * 3600
        )
    }

    /// Estimates a travel segment between two LocationInputs.
    /// Returns nil if coordinates are missing or if trip exceeds maximum allowed driving hours (2 days worth).
    static func estimate(
        from: LocationInput,
        to: LocationInput,
        constraints: DrivingConstraints
    ) -> TravelSegment? {

        guard let fromCoord = from.coordinate,
              let toCoord = to.coordinate else {
            return nil
        }

        let distanceMeters = haversineDistanceMeters(from: fromCoord, to: toCoord)
        let distanceMiles = distanceMeters * 0.000621371 * roadRoutingFactor
        let drivingHours = distanceMiles / averageSpeedMph

        // Maximum allowed: 5 days of driving (matches GameDAGRouter.maxDayLookahead)
        // This allows multi-day cross-country segments like Chicago → Anaheim
        let maxAllowedHours = constraints.maxDailyDrivingHours * 5.0
        if drivingHours > maxAllowedHours {
            return nil
        }

        return TravelSegment(
            fromLocation: from,
            toLocation: to,
            travelMode: .drive,
            distanceMeters: distanceMeters * roadRoutingFactor,
            durationSeconds: drivingHours * 3600
        )
    }

    // MARK: - Distance Calculations

    /// Calculates distance in miles between two stops.
    /// Uses Haversine formula if coordinates available, fallback otherwise.
    static func calculateDistanceMiles(
        from: ItineraryStop,
        to: ItineraryStop
    ) -> Double {
        if let fromCoord = from.coordinate,
           let toCoord = to.coordinate {
            return haversineDistanceMiles(from: fromCoord, to: toCoord) * roadRoutingFactor
        }
        return estimateFallbackDistance(from: from, to: to)
    }

    /// Calculates distance in miles between two coordinates using Haversine.
    static func haversineDistanceMiles(
        from: CLLocationCoordinate2D,
        to: CLLocationCoordinate2D
    ) -> Double {
        let earthRadiusMiles = 3958.8

        let lat1 = from.latitude * .pi / 180
        let lat2 = to.latitude * .pi / 180
        let deltaLat = (to.latitude - from.latitude) * .pi / 180
        let deltaLon = (to.longitude - from.longitude) * .pi / 180

        let a = sin(deltaLat / 2) * sin(deltaLat / 2) +
                cos(lat1) * cos(lat2) *
                sin(deltaLon / 2) * sin(deltaLon / 2)
        let c = 2 * atan2(sqrt(a), sqrt(1 - a))

        return earthRadiusMiles * c
    }

    /// Calculates distance in meters between two coordinates using Haversine.
    static func haversineDistanceMeters(
        from: CLLocationCoordinate2D,
        to: CLLocationCoordinate2D
    ) -> Double {
        let earthRadiusMeters = 6371000.0

        let lat1 = from.latitude * .pi / 180
        let lat2 = to.latitude * .pi / 180
        let deltaLat = (to.latitude - from.latitude) * .pi / 180
        let deltaLon = (to.longitude - from.longitude) * .pi / 180

        let a = sin(deltaLat / 2) * sin(deltaLat / 2) +
                cos(lat1) * cos(lat2) *
                sin(deltaLon / 2) * sin(deltaLon / 2)
        let c = 2 * atan2(sqrt(a), sqrt(1 - a))

        return earthRadiusMeters * c
    }

    /// Fallback distance when coordinates aren't available.
    static func estimateFallbackDistance(
        from: ItineraryStop,
        to: ItineraryStop
    ) -> Double {
        if from.city == to.city {
            return 0
        }
        return fallbackDistanceMiles
    }

    // MARK: - Travel Days

    /// Calculates which calendar days travel spans.
    static func calculateTravelDays(
        departure: Date,
        drivingHours: Double
    ) -> [Date] {
        var days: [Date] = []
        let calendar = Calendar.current

        let startDay = calendar.startOfDay(for: departure)
        days.append(startDay)

        // Add days if driving takes multiple days (8 hrs/day max)
        let daysOfDriving = max(1, Int(ceil(drivingHours / 8.0)))
        for dayOffset in 1..<daysOfDriving {
            if let nextDay = calendar.date(byAdding: .day, value: dayOffset, to: startDay) {
                days.append(nextDay)
            }
        }

        return days
    }

}
