//
//  CoreLocationProviderTests.swift
//  suixinkanTests
//

import CoreLocation
import XCTest
@testable import suixinkan

/// Core Location fallback 的授权、定位与逆地理编码测试。
@MainActor
final class CoreLocationProviderTests: XCTestCase {

    func testPrivacyAgreementIsRequiredBeforeLocation() async {
        let context = makeContext(privacyAccepted: false)

        await assertLocationError(.privacyNotAccepted) {
            _ = try await context.provider.requestCoordinate()
        }
        XCTAssertEqual(context.manager.requestLocationCallCount, 0)
    }

    func testDeniedAuthorizationReturnsPermissionError() async {
        let context = makeContext(authorizationStatus: .denied)

        await assertLocationError(.permissionDenied) {
            _ = try await context.provider.requestCoordinate()
        }
        XCTAssertEqual(context.manager.requestLocationCallCount, 0)
    }

    func testNotDeterminedAuthorizationContinuesAfterGrant() async throws {
        let coordinate = CLLocationCoordinate2D(latitude: 30.2741, longitude: 120.1551)
        let context = makeContext(
            authorizationStatus: .notDetermined,
            requestedAuthorizationStatus: .authorizedWhenInUse,
            locationResult: .success(CLLocation(
                latitude: coordinate.latitude,
                longitude: coordinate.longitude
            ))
        )

        let result = try await context.provider.requestCoordinate(
            desiredAccuracy: kCLLocationAccuracyHundredMeters
        )

        XCTAssertEqual(context.manager.requestAuthorizationCallCount, 1)
        XCTAssertEqual(context.manager.requestLocationCallCount, 1)
        XCTAssertEqual(context.manager.desiredAccuracy, kCLLocationAccuracyHundredMeters)
        XCTAssertEqual(result.latitude, coordinate.latitude, accuracy: 0.000_001)
        XCTAssertEqual(result.longitude, coordinate.longitude, accuracy: 0.000_001)
    }

    func testLocationFailureUsesExistingBusinessError() async {
        let context = makeContext(locationResult: .failure)

        await assertLocationError(.locationFailed) {
            _ = try await context.provider.requestCoordinate()
        }
    }

    func testSnapshotContainsReverseGeocodedAddress() async throws {
        let context = makeContext(
            locationResult: .success(CLLocation(latitude: 31.2304, longitude: 121.4737)),
            geocodedAddress: "上海市黄浦区"
        )

        let snapshot = try await context.provider.requestSnapshot()

        XCTAssertEqual(snapshot.latitude, 31.2304, accuracy: 0.000_001)
        XCTAssertEqual(snapshot.longitude, 121.4737, accuracy: 0.000_001)
        XCTAssertEqual(snapshot.address, "上海市黄浦区")
        XCTAssertEqual(context.geocoder.requestedCoordinates.count, 1)
    }

    func testSnapshotFallsBackToEmptyAddressWhenGeocoderHasNoResult() async throws {
        let context = makeContext(
            locationResult: .success(CLLocation(latitude: 39.9042, longitude: 116.4074)),
            geocodedAddress: ""
        )

        let snapshot = try await context.provider.requestSnapshot()

        XCTAssertEqual(snapshot.address, "")
    }

    private func makeContext(
        privacyAccepted: Bool = true,
        authorizationStatus: CLAuthorizationStatus = .authorizedWhenInUse,
        requestedAuthorizationStatus: CLAuthorizationStatus = .authorizedWhenInUse,
        locationResult: FakeCoreLocationManager.LocationResult = .success(
            CLLocation(latitude: 39.9042, longitude: 116.4074)
        ),
        geocodedAddress: String = "北京市东城区"
    ) -> TestContext {
        let suiteName = "CoreLocationProviderTests.\(UUID().uuidString)"
        let defaults = UserDefaults(suiteName: suiteName)!
        defaults.removePersistentDomain(forName: suiteName)
        let appStore = AppStore(defaults: defaults)
        appStore.session.privacyAgreementAccepted = privacyAccepted

        let manager = FakeCoreLocationManager(
            authorizationStatus: authorizationStatus,
            requestedAuthorizationStatus: requestedAuthorizationStatus,
            locationResult: locationResult
        )
        let geocoder = FakeLocationAddressGeocoder(address: geocodedAddress)
        let provider = CoreLocationProvider(
            appStore: appStore,
            manager: manager,
            geocoder: geocoder
        )
        return TestContext(provider: provider, manager: manager, geocoder: geocoder)
    }

    private func assertLocationError(
        _ expected: LocationProviderError,
        operation: () async throws -> Void
    ) async {
        do {
            try await operation()
            XCTFail("Expected \(expected)")
        } catch {
            XCTAssertEqual(error as? LocationProviderError, expected)
        }
    }
}

/// Core Location fallback 测试依赖集合。
@MainActor
private struct TestContext {
    let provider: CoreLocationProvider
    let manager: FakeCoreLocationManager
    let geocoder: FakeLocationAddressGeocoder
}

/// 可控的 `CLLocationManager` 替身。
@MainActor
private final class FakeCoreLocationManager: CoreLocationManaging {

    /// 单次定位请求的模拟结果。
    enum LocationResult {
        case success(CLLocation)
        case failure
    }

    var authorizationStatus: CLAuthorizationStatus
    var desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest
    weak var delegate: CLLocationManagerDelegate?
    private(set) var requestAuthorizationCallCount = 0
    private(set) var requestLocationCallCount = 0

    private let requestedAuthorizationStatus: CLAuthorizationStatus
    private let locationResult: LocationResult

    init(
        authorizationStatus: CLAuthorizationStatus,
        requestedAuthorizationStatus: CLAuthorizationStatus,
        locationResult: LocationResult
    ) {
        self.authorizationStatus = authorizationStatus
        self.requestedAuthorizationStatus = requestedAuthorizationStatus
        self.locationResult = locationResult
    }

    func requestWhenInUseAuthorization() {
        requestAuthorizationCallCount += 1
        authorizationStatus = requestedAuthorizationStatus
        delegate?.locationManagerDidChangeAuthorization?(CLLocationManager())
    }

    func requestLocation() {
        requestLocationCallCount += 1
        let callbackManager = CLLocationManager()
        switch locationResult {
        case let .success(location):
            delegate?.locationManager?(callbackManager, didUpdateLocations: [location])
        case .failure:
            delegate?.locationManager?(
                callbackManager,
                didFailWithError: CLError(.locationUnknown)
            )
        }
    }
}

/// 可控的逆地理编码替身。
@MainActor
private final class FakeLocationAddressGeocoder: LocationAddressGeocoding {
    private(set) var requestedCoordinates: [CLLocationCoordinate2D] = []
    private let address: String

    init(address: String) {
        self.address = address
    }

    func reverseGeocode(latitude: Double, longitude: Double) async -> String {
        requestedCoordinates.append(CLLocationCoordinate2D(
            latitude: latitude,
            longitude: longitude
        ))
        return address
    }
}
