//
//  LocationService.swift
//  SportsTime
//

import Foundation
import CoreLocation
import MapKit

actor LocationService {
    static let shared = LocationService()

    private init() {}

    // MARK: - Geocoding

    func geocode(_ address: String) async throws -> CLLocationCoordinate2D? {
        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = address
        request.resultTypes = .address

        let search = MKLocalSearch(request: request)
        let response = try await search.start()
        return response.mapItems.first?.location.coordinate
    }

    func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> String? {
        let request = MKLocalSearch.Request()
        request.region = MKCoordinateRegion(
            center: coordinate,
            latitudinalMeters: 100,
            longitudinalMeters: 100
        )
        request.resultTypes = .address

        let search = MKLocalSearch(request: request)
        let response = try await search.start()

        guard let item = response.mapItems.first else { return nil }
        return formatMapItem(item)
    }

    func resolveLocation(_ input: LocationInput) async throws -> LocationInput {
        if input.isResolved { return input }

        let searchText = input.address ?? input.name
        guard let coordinate = try await geocode(searchText) else {
            throw LocationError.geocodingFailed
        }

        return LocationInput(
            name: input.name,
            coordinate: coordinate,
            address: input.address
        )
    }

    // MARK: - Location Search

    func searchLocations(_ query: String) async throws -> [LocationSearchResult] {
        guard !query.trimmingCharacters(in: .whitespaces).isEmpty else {
            return []
        }

        let request = MKLocalSearch.Request()
        request.naturalLanguageQuery = query
        request.resultTypes = [.address, .pointOfInterest]

        let search = MKLocalSearch(request: request)
        let response = try await search.start()

        return response.mapItems.map { item in
            LocationSearchResult(
                name: item.name ?? "Unknown",
                address: formatMapItem(item),
                coordinate: item.location.coordinate
            )
        }
    }

    @available(iOS, deprecated: 26.0, message: "Uses placemark for address formatting")
    private func formatMapItem(_ item: MKMapItem) -> String {
        var components: [String] = []
        if let locality = item.placemark.locality {
            components.append(locality)
        }
        if let state = item.placemark.administrativeArea {
            components.append(state)
        }
        if let country = item.placemark.country, country != "United States" {
            components.append(country)
        }
        if components.isEmpty {
            return item.name ?? ""
        }
        return components.joined(separator: ", ")
    }

    // MARK: - Distance Calculations

    func calculateDistance(
        from: CLLocationCoordinate2D,
        to: CLLocationCoordinate2D
    ) -> CLLocationDistance {
        let fromLocation = CLLocation(latitude: from.latitude, longitude: from.longitude)
        let toLocation = CLLocation(latitude: to.latitude, longitude: to.longitude)
        return fromLocation.distance(from: toLocation)
    }

    func calculateDrivingRoute(
        from: CLLocationCoordinate2D,
        to: CLLocationCoordinate2D
    ) async throws -> RouteInfo {
        let request = MKDirections.Request()
        let fromLocation = CLLocation(latitude: from.latitude, longitude: from.longitude)
        let toLocation = CLLocation(latitude: to.latitude, longitude: to.longitude)
        request.source = MKMapItem(location: fromLocation, address: nil)
        request.destination = MKMapItem(location: toLocation, address: nil)
        request.transportType = .automobile
        request.requestsAlternateRoutes = false

        let directions = MKDirections(request: request)
        let response = try await directions.calculate()

        guard let route = response.routes.first else {
            throw LocationError.routeNotFound
        }

        return RouteInfo(
            distance: route.distance,
            expectedTravelTime: route.expectedTravelTime,
            polyline: route.polyline
        )
    }

    func calculateDrivingMatrix(
        origins: [CLLocationCoordinate2D],
        destinations: [CLLocationCoordinate2D]
    ) async throws -> [[RouteInfo?]] {
        var matrix: [[RouteInfo?]] = []

        for origin in origins {
            var row: [RouteInfo?] = []
            for destination in destinations {
                do {
                    let route = try await calculateDrivingRoute(from: origin, to: destination)
                    row.append(route)
                } catch {
                    row.append(nil)
                }
            }
            matrix.append(row)
        }

        return matrix
    }
}

// MARK: - Route Info

struct RouteInfo {
    let distance: CLLocationDistance  // meters
    let expectedTravelTime: TimeInterval  // seconds
    let polyline: MKPolyline?

    var distanceMiles: Double { distance * 0.000621371 }
    var travelTimeHours: Double { expectedTravelTime / 3600.0 }
}

// MARK: - Location Search Result

struct LocationSearchResult: Identifiable, Hashable {
    let id = UUID()
    let name: String
    let address: String
    let coordinate: CLLocationCoordinate2D

    var displayName: String {
        if address.isEmpty || name == address {
            return name
        }
        return "\(name), \(address)"
    }

    func toLocationInput() -> LocationInput {
        LocationInput(
            name: name,
            coordinate: coordinate,
            address: address.isEmpty ? nil : address
        )
    }
}

// MARK: - Errors

enum LocationError: Error, LocalizedError {
    case geocodingFailed
    case routeNotFound
    case permissionDenied

    var errorDescription: String? {
        switch self {
        case .geocodingFailed: return "Unable to find location"
        case .routeNotFound: return "Unable to calculate route"
        case .permissionDenied: return "Location permission required"
        }
    }
}
