import UIKit
import CoreBluetooth
import CoreLocation

/// Beacon scanner for task auto-completion using the Payfrit shard system.
/// Scans for ALL shard UUIDs and resolves via API to find the target ServicePoint.
final class BeaconScanner: NSObject, ObservableObject {
    private let targetServicePointId: Int
    private let onBeaconDetected: (Double) -> Void
    private let onBluetoothOff: (() -> Void)?
    private let onPermissionDenied: (() -> Void)?

    @Published var isScanning = false

    private var locationManager: CLLocationManager?
    private var activeConstraints: [CLBeaconIdentityConstraint] = []
    private var checkTimer: Timer?

    // RSSI samples for dwell time enforcement
    private var rssiSamples: [Int] = []
    private let minSamplesToConfirm = 30  // ~3 seconds to match Android (30 samples at ~100ms)
    private let rssiThreshold = -75

    // Track resolved beacons to avoid repeated API calls
    private var resolvedBeacons: [String: Int] = [:] // "uuid-major-minor" -> servicePointId
    private var pendingResolutions: Set<String> = []

    init(targetServicePointId: Int,
         onBeaconDetected: @escaping (Double) -> Void,
         onBluetoothOff: (() -> Void)? = nil,
         onPermissionDenied: (() -> Void)? = nil) {
        self.targetServicePointId = targetServicePointId
        self.onBeaconDetected = onBeaconDetected
        self.onBluetoothOff = onBluetoothOff
        self.onPermissionDenied = onPermissionDenied
        super.init()
    }

    // Legacy init for backwards compatibility with UUID-based scanning
    convenience init(targetUUID: String,
                     onBeaconDetected: @escaping (Double) -> Void,
                     onBluetoothOff: (() -> Void)? = nil,
                     onPermissionDenied: (() -> Void)? = nil) {
        // Use 0 as a placeholder - this path won't use ServicePoint matching
        self.init(targetServicePointId: 0,
                  onBeaconDetected: onBeaconDetected,
                  onBluetoothOff: onBluetoothOff,
                  onPermissionDenied: onPermissionDenied)
    }

    // MARK: - Start/Stop

    func startScanning() {
        guard !isScanning else { return }

        locationManager = CLLocationManager()
        locationManager?.delegate = self

        let status = locationManager!.authorizationStatus
        if status == .notDetermined {
            locationManager?.requestWhenInUseAuthorization()
            return
        }
        guard status == .authorizedWhenInUse || status == .authorizedAlways else {
            onPermissionDenied?()
            return
        }

        // Start ranging for ALL shard UUIDs
        for uuid in BeaconShardPool.uuids {
            let constraint = CLBeaconIdentityConstraint(uuid: uuid)
            activeConstraints.append(constraint)
            locationManager?.startRangingBeacons(satisfying: constraint)
        }

        isScanning = true
        rssiSamples.removeAll()

        // Keep screen on during scanning
        DispatchQueue.main.async {
            UIApplication.shared.isIdleTimerDisabled = true
        }

        // Periodic Bluetooth check
        checkTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { [weak self] _ in
            if CBCentralManager.authorization == .denied ||
               CBCentralManager.authorization == .restricted {
                self?.onBluetoothOff?()
            }
        }

        print("[BeaconScanner] Started scanning for \(BeaconShardPool.uuids.count) shard UUIDs, target ServicePointId: \(targetServicePointId)")
    }

    func stopScanning() {
        isScanning = false
        for constraint in activeConstraints {
            locationManager?.stopRangingBeacons(satisfying: constraint)
        }
        activeConstraints.removeAll()
        checkTimer?.invalidate()
        checkTimer = nil
        rssiSamples.removeAll()
        DispatchQueue.main.async {
            UIApplication.shared.isIdleTimerDisabled = false
        }
    }

    func resetSamples() {
        rssiSamples.removeAll()
    }

    func dispose() {
        stopScanning()
        locationManager = nil
    }

    // MARK: - Beacon Resolution

    private func beaconKey(_ beacon: CLBeacon) -> String {
        return "\(beacon.uuid.uuidString)-\(beacon.major)-\(beacon.minor)"
    }

    private func resolveBeacon(_ beacon: CLBeacon) {
        let key = beaconKey(beacon)
        guard resolvedBeacons[key] == nil && !pendingResolutions.contains(key) else { return }

        pendingResolutions.insert(key)

        Task {
            do {
                let servicePointId = try await APIService.shared.resolveServicePoint(
                    uuid: beacon.uuid.uuidString,
                    major: beacon.major.intValue,
                    minor: beacon.minor.intValue
                )
                await MainActor.run {
                    self.resolvedBeacons[key] = servicePointId
                    self.pendingResolutions.remove(key)
                    print("[BeaconScanner] Resolved \(key) -> ServicePointId \(servicePointId)")
                }
            } catch {
                await MainActor.run {
                    self.pendingResolutions.remove(key)
                    print("[BeaconScanner] Failed to resolve \(key): \(error)")
                }
            }
        }
    }
}

// MARK: - CLLocationManagerDelegate

extension BeaconScanner: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon],
                         satisfying constraint: CLBeaconIdentityConstraint) {
        var foundTarget = false

        for beacon in beacons {
            let rssi = beacon.rssi
            guard rssi != 0 else { continue }

            // Check if this is a Payfrit beacon
            guard BeaconShardPool.isPayfrit(beacon.uuid) else { continue }

            let key = beaconKey(beacon)

            // Try to resolve if not already resolved
            if resolvedBeacons[key] == nil {
                resolveBeacon(beacon)
                continue
            }

            // Check if this is our target service point
            guard resolvedBeacons[key] == targetServicePointId else { continue }

            foundTarget = true

            if rssi >= rssiThreshold {
                rssiSamples.append(rssi)

                if rssiSamples.count >= minSamplesToConfirm {
                    let avg = Double(rssiSamples.reduce(0, +)) / Double(rssiSamples.count)
                    print("[BeaconScanner] Target beacon confirmed! Avg RSSI: \(avg)")
                    DispatchQueue.main.async { [weak self] in
                        self?.onBeaconDetected(avg)
                    }
                }
            } else {
                // Signal too weak, reset
                if !rssiSamples.isEmpty { rssiSamples.removeAll() }
            }
        }

        // Target beacon lost this cycle
        if !foundTarget && !rssiSamples.isEmpty {
            rssiSamples.removeAll()
        }
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        let status = manager.authorizationStatus
        if status == .authorizedWhenInUse || status == .authorizedAlways {
            if !isScanning {
                startScanning()
            }
        } else if status == .denied || status == .restricted {
            onPermissionDenied?()
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailRangingFor constraint: CLBeaconIdentityConstraint, error: Error) {
        print("[BeaconScanner] Ranging failed: \(error)")
    }
}
