import UIKit
import CoreBluetooth
import CoreLocation

/// Beacon scanner for task auto-completion.
/// Scans for a specific UUID and triggers callback after dwell time is met.
final class BeaconScanner: NSObject, ObservableObject {
    private let targetUUID: String
    private let onBeaconDetected: (Double) -> Void
    private let onBluetoothOff: (() -> Void)?
    private let onPermissionDenied: (() -> Void)?

    @Published var isScanning = false

    private var locationManager: CLLocationManager?
    private var activeConstraint: CLBeaconIdentityConstraint?
    private var checkTimer: Timer?

    // RSSI samples for dwell time enforcement
    private var rssiSamples: [Int] = []
    private let minSamplesToConfirm = 5  // ~5 seconds
    private let rssiThreshold = -75

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

    // MARK: - UUID formatting

    private func formatUUID(_ uuid: String) -> String {
        let clean = uuid.replacingOccurrences(of: "-", with: "").uppercased()
        guard clean.count == 32 else { return uuid }
        let i = clean.startIndex
        let p1 = clean[i..<clean.index(i, offsetBy: 8)]
        let p2 = clean[clean.index(i, offsetBy: 8)..<clean.index(i, offsetBy: 12)]
        let p3 = clean[clean.index(i, offsetBy: 12)..<clean.index(i, offsetBy: 16)]
        let p4 = clean[clean.index(i, offsetBy: 16)..<clean.index(i, offsetBy: 20)]
        let p5 = clean[clean.index(i, offsetBy: 20)..<clean.index(i, offsetBy: 32)]
        return "\(p1)-\(p2)-\(p3)-\(p4)-\(p5)"
    }

    // MARK: - Start/Stop

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

        let formatted = formatUUID(targetUUID)
        guard let uuid = UUID(uuidString: formatted) else { return }

        locationManager = CLLocationManager()
        locationManager?.delegate = self

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

        let constraint = CLBeaconIdentityConstraint(uuid: uuid)
        activeConstraint = constraint
        locationManager?.startRangingBeacons(satisfying: constraint)

        isScanning = true
        rssiSamples.removeAll()

        // Idle timer disabled so screen stays 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?()
            }
        }
    }

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

    func resetSamples() {
        rssiSamples.removeAll()
    }

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

// MARK: - CLLocationManagerDelegate

extension BeaconScanner: CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon],
                         satisfying constraint: CLBeaconIdentityConstraint) {
        let normalizedTarget = targetUUID.replacingOccurrences(of: "-", with: "").uppercased()
        var foundThisCycle = false

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

            let detectedUUID = beacon.uuid.uuidString.replacingOccurrences(of: "-", with: "").uppercased()
            guard detectedUUID == normalizedTarget else { continue }

            foundThisCycle = true

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

                if rssiSamples.count >= minSamplesToConfirm {
                    let avg = Double(rssiSamples.reduce(0, +)) / Double(rssiSamples.count)
                    DispatchQueue.main.async { [weak self] in
                        self?.onBeaconDetected(avg)
                    }
                }
            } else {
                // Signal too weak, reset
                if !rssiSamples.isEmpty { rssiSamples.removeAll() }
            }
        }

        // Beacon lost this cycle
        if !foundThisCycle && !rssiSamples.isEmpty {
            rssiSamples.removeAll()
        }
    }

    func locationManager(_ manager: CLLocationManager, didFailRangingFor constraint: CLBeaconIdentityConstraint, error: Error) {
        // Ranging failed - could be Bluetooth off
    }
}
