//
//  LocationProvider.swift
//  suixinkan
//

import CoreLocation
import Foundation

/// Core Location 管理器最小接口，便于模拟器定位实现注入测试替身。
@MainActor
protocol CoreLocationManaging: AnyObject {
    var authorizationStatus: CLAuthorizationStatus { get }
    var desiredAccuracy: CLLocationAccuracy { get set }
    var delegate: CLLocationManagerDelegate? { get set }

    /// 请求使用期间定位权限。
    func requestWhenInUseAuthorization()
    /// 请求一次当前位置。
    func requestLocation()
}

extension CLLocationManager: CoreLocationManaging {}

/// Core Location 定位实现，供模拟器运行并支持依赖注入测试。
@MainActor
final class CoreLocationProvider: NSObject, LocationProviding, CLLocationManagerDelegate {

    nonisolated static let shared = CoreLocationProvider()

    private var appStore: AppStore = .shared
    private var manager: any CoreLocationManaging = CLLocationManager()
    private var geocoder: any LocationAddressGeocoding = LocationGeocoder.shared
    private var permissionContinuation: CheckedContinuation<Void, Error>?
    private var locationContinuation: CheckedContinuation<CLLocationCoordinate2D, Error>?

    nonisolated override init() {
        super.init()
    }

    /// 使用可控依赖创建 Core Location 定位服务。
    init(
        appStore: AppStore,
        manager: any CoreLocationManaging,
        geocoder: any LocationAddressGeocoding
    ) {
        self.appStore = appStore
        self.manager = manager
        self.geocoder = geocoder
        super.init()
        manager.delegate = self
    }

    /// 请求坐标及逆地理编码地址。
    func requestSnapshot(
        desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest
    ) async throws -> HomeLocationSnapshot {
        let coordinate = try await requestCoordinate(desiredAccuracy: desiredAccuracy)
        let address = await geocoder.reverseGeocode(
            latitude: coordinate.latitude,
            longitude: coordinate.longitude
        )
        return HomeLocationSnapshot(
            latitude: coordinate.latitude,
            longitude: coordinate.longitude,
            address: address
        )
    }

    /// 请求一次当前位置坐标。
    func requestCoordinate(
        desiredAccuracy: CLLocationAccuracy
    ) async throws -> CLLocationCoordinate2D {
        try requirePrivacyAgreement()
        try await ensureLocationPermission()
        guard locationContinuation == nil else {
            throw LocationProviderError.locationFailed
        }

        manager.delegate = self
        manager.desiredAccuracy = desiredAccuracy
        return try await withCheckedThrowingContinuation { continuation in
            locationContinuation = continuation
            manager.requestLocation()
        }
    }

    /// 使用系统服务将坐标解析为地址。
    func reverseGeocode(latitude: Double, longitude: Double) async -> String {
        guard appStore.session.privacyAgreementAccepted else { return "" }
        return await geocoder.reverseGeocode(latitude: latitude, longitude: longitude)
    }

    /// 请求系统定位权限，等待用户完成首次授权。
    func ensureLocationPermission() async throws {
        manager.delegate = self
        switch manager.authorizationStatus {
        case .authorizedAlways, .authorizedWhenInUse:
            return
        case .denied, .restricted:
            throw LocationProviderError.permissionDenied
        case .notDetermined:
            guard permissionContinuation == nil else {
                throw LocationProviderError.locationFailed
            }
            try await withCheckedThrowingContinuation { continuation in
                permissionContinuation = continuation
                manager.requestWhenInUseAuthorization()
            }
        @unknown default:
            throw LocationProviderError.permissionDenied
        }
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        resolvePermission(status: self.manager.authorizationStatus)
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        guard let continuation = locationContinuation else { return }
        locationContinuation = nil
        guard let coordinate = locations.last?.coordinate,
              CLLocationCoordinate2DIsValid(coordinate),
              coordinate.latitude != 0 || coordinate.longitude != 0 else {
            continuation.resume(throwing: LocationProviderError.locationFailed)
            return
        }
        continuation.resume(returning: coordinate)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        guard let continuation = locationContinuation else { return }
        locationContinuation = nil
        continuation.resume(throwing: LocationProviderError.locationFailed)
    }

    private func requirePrivacyAgreement() throws {
        guard appStore.session.privacyAgreementAccepted else {
            throw LocationProviderError.privacyNotAccepted
        }
    }

    private func resolvePermission(status: CLAuthorizationStatus) {
        guard let continuation = permissionContinuation else { return }
        switch status {
        case .authorizedAlways, .authorizedWhenInUse:
            permissionContinuation = nil
            continuation.resume()
        case .denied, .restricted:
            permissionContinuation = nil
            continuation.resume(throwing: LocationProviderError.permissionDenied)
        case .notDetermined:
            break
        @unknown default:
            permissionContinuation = nil
            continuation.resume(throwing: LocationProviderError.permissionDenied)
        }
    }
}

#if targetEnvironment(simulator)

/// 模拟器默认定位服务，使用 Core Location 与系统逆地理编码。
typealias LocationProvider = CoreLocationProvider

#else

/// 真机统一定位服务，封装高德 `AMapLocationManager`，对齐 Android `LocationProvider`。
@MainActor
final class LocationProvider: NSObject, LocationProviding {

    nonisolated static let shared = LocationProvider()

    private let permissionManager = CLLocationManager()
    private let geocoder = LocationGeocoder.shared

    nonisolated override init() {
        super.init()
    }

    /// 请求坐标及高德逆地理编码地址。
    func requestSnapshot(desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest) async throws -> HomeLocationSnapshot {
        try AMapBootstrap.requireConfigured()
        try await ensureLocationPermission()

        let manager = makeLocationManager(
            desiredAccuracy: desiredAccuracy,
            locatingWithReGeocode: true
        )
        return try await withCheckedThrowingContinuation { continuation in
            let started = manager.requestLocation(withReGeocode: true) { location, regeocode, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let location else {
                    continuation.resume(throwing: LocationProviderError.locationFailed)
                    return
                }
                let address = regeocode?.formattedAddress ?? ""
                continuation.resume(returning: HomeLocationSnapshot(
                    latitude: location.coordinate.latitude,
                    longitude: location.coordinate.longitude,
                    address: address
                ))
            }
            if !started {
                continuation.resume(throwing: LocationProviderError.locationFailed)
            }
        }
    }

    /// 请求一次当前位置坐标。
    func requestCoordinate(desiredAccuracy: CLLocationAccuracy) async throws -> CLLocationCoordinate2D {
        try AMapBootstrap.requireConfigured()
        try await ensureLocationPermission()

        let manager = makeLocationManager(
            desiredAccuracy: desiredAccuracy,
            locatingWithReGeocode: false
        )
        return try await withCheckedThrowingContinuation { continuation in
            let started = manager.requestLocation(withReGeocode: false) { location, _, error in
                if let error {
                    continuation.resume(throwing: error)
                    return
                }
                guard let location else {
                    continuation.resume(throwing: LocationProviderError.locationFailed)
                    return
                }
                continuation.resume(returning: location.coordinate)
            }
            if !started {
                continuation.resume(throwing: LocationProviderError.locationFailed)
            }
        }
    }

    /// 使用高德服务将坐标解析为地址。
    func reverseGeocode(latitude: Double, longitude: Double) async -> String {
        await geocoder.reverseGeocode(latitude: latitude, longitude: longitude)
    }

    /// 请求系统定位权限，首次弹窗后等待用户响应。
    func ensureLocationPermission() async throws {
        permissionManager.desiredAccuracy = kCLLocationAccuracyBest
        let initialStatus = permissionManager.authorizationStatus
        if initialStatus == .notDetermined {
            permissionManager.requestWhenInUseAuthorization()
            try await Task.sleep(nanoseconds: 500_000_000)
        }

        let status = permissionManager.authorizationStatus
        guard status == .authorizedWhenInUse || status == .authorizedAlways else {
            throw LocationProviderError.permissionDenied
        }
    }

    private func makeLocationManager(
        desiredAccuracy: CLLocationAccuracy,
        locatingWithReGeocode: Bool
    ) -> AMapLocationManager {
        let manager = AMapLocationManager()
        manager.desiredAccuracy = desiredAccuracy
        manager.locationTimeout = 30
        manager.reGeocodeTimeout = 10
        manager.locatingWithReGeocode = locatingWithReGeocode
        return manager
    }
}

#endif
