import Foundation
import Observation
import CoreLocation

/// Wraps CLLocationManager for live Gassi-Tracking with background updates.
@Observable
@MainActor
final class LocationTracker: NSObject, CLLocationManagerDelegate {
    private let manager = CLLocationManager()

    var points: [GPSPoint] = []
    var isTracking: Bool = false
    var isPaused: Bool = false
    var isAutoPaused: Bool = false
    var startedAt: Date?
    var pausedAt: Date?
    var accumulatedPausedSeconds: Int = 0
    var accumulatedAutoPausedSeconds: Int = 0
    var authorizationStatus: CLAuthorizationStatus
    var permissionDenied: Bool = false

    private var pendingStart: Bool = false
    private var lastPointAt: Date?
    private var autoPauseStartedAt: Date?

    override init() {
        self.authorizationStatus = manager.authorizationStatus
        super.init()
        manager.delegate = self
        manager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        manager.distanceFilter = 5
        manager.activityType = .fitness
        manager.pausesLocationUpdatesAutomatically = false
    }

    // MARK: - Lifecycle

    func startOrRequest() {
        permissionDenied = false
        switch authorizationStatus {
        case .notDetermined:
            pendingStart = true
            manager.requestWhenInUseAuthorization()
        case .denied, .restricted:
            permissionDenied = true
        case .authorizedWhenInUse, .authorizedAlways:
            beginFresh()
        @unknown default:
            permissionDenied = true
        }
    }

    /// Restore an in-progress walk that was interrupted by an app restart.
    /// Treats the elapsed offline time as pause time so the active-tracking
    /// counter doesn't jump.
    func restore(
        startedAt: Date,
        points: [GPSPoint],
        accumulatedPausedSeconds: Int,
        lastUpdate: Date,
        wasPaused: Bool
    ) {
        self.startedAt = startedAt
        self.points = points
        // The offline gap counts as paused time.
        let offlineGap = max(0, Int(Date.now.timeIntervalSince(lastUpdate)))
        self.accumulatedPausedSeconds = accumulatedPausedSeconds + offlineGap
        self.isTracking = true

        if wasPaused {
            self.isPaused = true
            self.pausedAt = nil // the previous pause is rolled into accumulated
        } else {
            self.isPaused = false
            beginUpdates()
        }
    }

    func pause() {
        guard isTracking, !isPaused else { return }
        isPaused = true
        pausedAt = .now
        manager.stopUpdatingLocation()
    }

    func resume() {
        guard isPaused else { return }
        if let pausedAt {
            accumulatedPausedSeconds += Int(Date.now.timeIntervalSince(pausedAt))
        }
        pausedAt = nil
        isPaused = false
        beginUpdates()
    }

    func stop() {
        if isPaused, let pausedAt {
            accumulatedPausedSeconds += Int(Date.now.timeIntervalSince(pausedAt))
        }
        endAutoPauseIfNeeded()
        isPaused = false
        pausedAt = nil
        manager.stopUpdatingLocation()
        manager.allowsBackgroundLocationUpdates = false
        isTracking = false
    }

    // MARK: - Derived state

    /// Active tracking time in seconds, with both manual and auto pauses removed.
    var effectiveElapsedSeconds: Int {
        guard let startedAt else { return 0 }
        let total = Int(Date.now.timeIntervalSince(startedAt))
        var paused = accumulatedPausedSeconds + accumulatedAutoPausedSeconds
        if isPaused, let pausedAt {
            paused += Int(Date.now.timeIntervalSince(pausedAt))
        }
        if isAutoPaused, let autoPauseStartedAt {
            paused += Int(Date.now.timeIntervalSince(autoPauseStartedAt))
        }
        return max(0, total - paused)
    }

    /// Called from a timer in TrackingView. If no new GPS point arrived in the
    /// last 2 minutes, auto-pause the duration counter without stopping GPS —
    /// so we can detect the resume from the next location update.
    func checkAutoPause() {
        guard isTracking, !isPaused, !isAutoPaused else { return }
        let enabled = UserDefaults.standard.object(forKey: "autoPauseEnabled") as? Bool ?? true
        guard enabled else { return }
        guard let last = lastPointAt else { return }
        if Date.now.timeIntervalSince(last) > 120 {
            isAutoPaused = true
            autoPauseStartedAt = .now
        }
    }

    private func endAutoPauseIfNeeded() {
        guard isAutoPaused else { return }
        if let started = autoPauseStartedAt {
            accumulatedAutoPausedSeconds += Int(Date.now.timeIntervalSince(started))
        }
        isAutoPaused = false
        autoPauseStartedAt = nil
    }

    var totalDistanceMeters: Double {
        guard points.count >= 2 else { return 0 }
        var total: Double = 0
        for i in 1..<points.count {
            let a = CLLocation(latitude: points[i - 1].lat, longitude: points[i - 1].lon)
            let b = CLLocation(latitude: points[i].lat, longitude: points[i].lon)
            total += b.distance(from: a)
        }
        return total
    }

    // MARK: - Internal

    private func beginFresh() {
        points = []
        startedAt = .now
        pausedAt = nil
        accumulatedPausedSeconds = 0
        isPaused = false
        isTracking = true
        beginUpdates()
        if authorizationStatus == .authorizedWhenInUse {
            manager.requestAlwaysAuthorization()
        }
    }

    private func beginUpdates() {
        manager.allowsBackgroundLocationUpdates = true
        manager.showsBackgroundLocationIndicator = true
        manager.startUpdatingLocation()
    }

    // MARK: - CLLocationManagerDelegate

    nonisolated func locationManager(
        _ manager: CLLocationManager,
        didUpdateLocations locations: [CLLocation]
    ) {
        let newPoints = locations
            .filter { $0.horizontalAccuracy >= 0 && $0.horizontalAccuracy < 30 }
            .map { GPSPoint(lat: $0.coordinate.latitude, lon: $0.coordinate.longitude, alt: $0.altitude) }
        guard !newPoints.isEmpty else { return }
        Task { @MainActor in
            guard self.isTracking, !self.isPaused else { return }

            // If we were auto-paused, the very arrival of a new GPS update
            // means movement resumed.
            self.endAutoPauseIfNeeded()

            self.points.append(contentsOf: newPoints)
            self.lastPointAt = .now
        }
    }

    nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        let status = manager.authorizationStatus
        Task { @MainActor in
            self.authorizationStatus = status
            switch status {
            case .denied, .restricted:
                self.pendingStart = false
                self.permissionDenied = true
            case .authorizedWhenInUse, .authorizedAlways:
                if self.pendingStart {
                    self.pendingStart = false
                    self.beginFresh()
                }
            default:
                break
            }
        }
    }

    nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("LocationTracker error: \(error)")
    }
}
