//
//  BruteForceRouteVerifier.swift
//  SportsTimeTests
//
//  Exhaustively enumerates all route permutations to verify optimality.
//  Used for inputs with ≤8 stops where brute force is feasible.
//

import Foundation
import CoreLocation
@testable import SportsTime

// MARK: - Route Verifier

struct BruteForceRouteVerifier {

    // MARK: - Route Comparison Result

    struct VerificationResult {
        let isOptimal: Bool
        let proposedRouteDistance: Double
        let optimalRouteDistance: Double
        let optimalRoute: [String]?
        let improvement: Double? // Percentage improvement if not optimal
        let permutationsChecked: Int

        var improvementPercentage: Double? {
            guard let improvement = improvement else { return nil }
            return improvement * 100
        }
    }

    // MARK: - Verification

    /// Verify that a proposed route is optimal (or near-optimal) by checking all permutations
    /// - Parameters:
    ///   - proposedRoute: The route to verify (ordered list of stop IDs)
    ///   - stops: Dictionary mapping stop IDs to their coordinates
    ///   - tolerance: Percentage tolerance for "near-optimal" (default 0 = must be exactly optimal)
    /// - Returns: Verification result
    static func verify(
        proposedRoute: [String],
        stops: [String: CLLocationCoordinate2D],
        tolerance: Double = 0
    ) -> VerificationResult {
        guard proposedRoute.count <= TestConstants.bruteForceMaxStops else {
            fatalError("BruteForceRouteVerifier should only be used for ≤\(TestConstants.bruteForceMaxStops) stops")
        }

        guard proposedRoute.count >= 2 else {
            // Single stop or empty - trivially optimal
            return VerificationResult(
                isOptimal: true,
                proposedRouteDistance: 0,
                optimalRouteDistance: 0,
                optimalRoute: proposedRoute,
                improvement: nil,
                permutationsChecked: 1
            )
        }

        let proposedDistance = calculateRouteDistance(proposedRoute, stops: stops)

        // Find optimal route by checking all permutations
        let allPermutations = permutations(of: proposedRoute)
        var optimalDistance = Double.infinity
        var optimalRoute: [String] = []

        for permutation in allPermutations {
            let distance = calculateRouteDistance(permutation, stops: stops)
            if distance < optimalDistance {
                optimalDistance = distance
                optimalRoute = permutation
            }
        }

        let isOptimal: Bool
        var improvement: Double? = nil

        if tolerance == 0 {
            // Exact optimality check with floating point tolerance
            isOptimal = abs(proposedDistance - optimalDistance) < 0.001
        } else {
            // Within tolerance
            let maxAllowed = optimalDistance * (1 + tolerance)
            isOptimal = proposedDistance <= maxAllowed
        }

        if !isOptimal && optimalDistance > 0 {
            improvement = (proposedDistance - optimalDistance) / optimalDistance
        }

        return VerificationResult(
            isOptimal: isOptimal,
            proposedRouteDistance: proposedDistance,
            optimalRouteDistance: optimalDistance,
            optimalRoute: optimalRoute,
            improvement: improvement,
            permutationsChecked: allPermutations.count
        )
    }

    /// Verify a route is optimal with a fixed start and end point
    static func verifyWithFixedEndpoints(
        proposedRoute: [String],
        stops: [String: CLLocationCoordinate2D],
        startId: String,
        endId: String,
        tolerance: Double = 0
    ) -> VerificationResult {
        guard proposedRoute.first == startId && proposedRoute.last == endId else {
            // Invalid route - doesn't match required endpoints
            return VerificationResult(
                isOptimal: false,
                proposedRouteDistance: Double.infinity,
                optimalRouteDistance: 0,
                optimalRoute: nil,
                improvement: nil,
                permutationsChecked: 0
            )
        }

        // Get intermediate stops (excluding start and end)
        let intermediateStops = proposedRoute.dropFirst().dropLast()

        guard intermediateStops.count <= TestConstants.bruteForceMaxStops - 2 else {
            fatalError("BruteForceRouteVerifier: too many intermediate stops")
        }

        let proposedDistance = calculateRouteDistance(proposedRoute, stops: stops)

        // Generate all permutations of intermediate stops
        let allPermutations = permutations(of: Array(intermediateStops))
        var optimalDistance = Double.infinity
        var optimalRoute: [String] = []

        for permutation in allPermutations {
            var fullRoute = [startId]
            fullRoute.append(contentsOf: permutation)
            fullRoute.append(endId)

            let distance = calculateRouteDistance(fullRoute, stops: stops)
            if distance < optimalDistance {
                optimalDistance = distance
                optimalRoute = fullRoute
            }
        }

        let isOptimal: Bool
        var improvement: Double? = nil

        if tolerance == 0 {
            isOptimal = abs(proposedDistance - optimalDistance) < 0.001
        } else {
            let maxAllowed = optimalDistance * (1 + tolerance)
            isOptimal = proposedDistance <= maxAllowed
        }

        if !isOptimal && optimalDistance > 0 {
            improvement = (proposedDistance - optimalDistance) / optimalDistance
        }

        return VerificationResult(
            isOptimal: isOptimal,
            proposedRouteDistance: proposedDistance,
            optimalRouteDistance: optimalDistance,
            optimalRoute: optimalRoute,
            improvement: improvement,
            permutationsChecked: allPermutations.count
        )
    }

    /// Check if there's an obviously better route (significantly shorter)
    static func hasObviouslyBetterRoute(
        proposedRoute: [String],
        stops: [String: CLLocationCoordinate2D],
        threshold: Double = 0.1 // 10% improvement threshold
    ) -> (hasBetter: Bool, improvement: Double?) {
        let result = verify(proposedRoute: proposedRoute, stops: stops, tolerance: threshold)
        return (!result.isOptimal, result.improvement)
    }

    // MARK: - Distance Calculation

    /// Calculate total route distance using haversine formula
    static func calculateRouteDistance(
        _ route: [String],
        stops: [String: CLLocationCoordinate2D]
    ) -> Double {
        guard route.count >= 2 else { return 0 }

        var totalDistance: Double = 0

        for i in 0..<(route.count - 1) {
            guard let from = stops[route[i]],
                  let to = stops[route[i + 1]] else {
                continue
            }
            totalDistance += haversineDistanceMiles(from: from, to: to)
        }

        return totalDistance
    }

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

        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
    }

    // MARK: - Permutation Generation

    /// Generate all permutations of an array (Heap's algorithm)
    static func permutations<T>(of array: [T]) -> [[T]] {
        var result: [[T]] = []
        var arr = array

        func generate(_ n: Int) {
            if n == 1 {
                result.append(arr)
                return
            }

            for i in 0..<n {
                generate(n - 1)
                if n % 2 == 0 {
                    arr.swapAt(i, n - 1)
                } else {
                    arr.swapAt(0, n - 1)
                }
            }
        }

        generate(array.count)
        return result
    }

    // MARK: - Factorial

    /// Calculate factorial (for estimating permutation count)
    static func factorial(_ n: Int) -> Int {
        guard n > 1 else { return 1 }
        return (1...n).reduce(1, *)
    }
}

// MARK: - Convenience Extensions

extension BruteForceRouteVerifier {
    /// Verify a trip's route is optimal
    static func verifyTrip(_ trip: Trip) -> VerificationResult {
        var stops: [String: CLLocationCoordinate2D] = [:]

        for stop in trip.stops {
            if let coord = stop.coordinate {
                stops[stop.id.uuidString] = coord
            }
        }

        let routeIds = trip.stops.map { $0.id.uuidString }
        return verify(proposedRoute: routeIds, stops: stops)
    }

    /// Verify a list of stadiums forms an optimal route
    static func verifyStadiumRoute(_ stadiums: [Stadium]) -> VerificationResult {
        let stops = Dictionary(uniqueKeysWithValues: stadiums.map { ($0.id, $0.coordinate) })
        let routeIds = stadiums.map { $0.id }
        return verify(proposedRoute: routeIds, stops: stops)
    }
}

// MARK: - Test Assertions

extension BruteForceRouteVerifier.VerificationResult {
    /// Returns a detailed failure message if not optimal
    var failureMessage: String? {
        guard !isOptimal else { return nil }

        var message = "Route is not optimal. "
        message += "Proposed: \(String(format: "%.1f", proposedRouteDistance)) miles, "
        message += "Optimal: \(String(format: "%.1f", optimalRouteDistance)) miles"

        if let improvement = improvementPercentage {
            message += " (\(String(format: "%.1f", improvement))% longer)"
        }

        message += ". Checked \(permutationsChecked) permutations."

        return message
    }
}
