import CoreBluetooth
import Combine

class BluetoothManager: NSObject, ObservableObject {

    // MARK: Published
    @Published var bluetoothState: CBManagerState = .unknown
    @Published var isScanning = false
    @Published var discoveredPeripherals: [PeripheralInfo] = []
    @Published var connectedPeripheral: CBPeripheral?
    @Published var services: [CBService] = []
    @Published var terminalLog: [TerminalEntry] = []
    @Published var selectedWriteCharacteristic: CBCharacteristic?
    @Published var subscribedCharacteristics: [CBCharacteristic] = []

    // MARK: Nordic UART Service (NUS) — most common BLE UART
    static let nusServiceUUID = CBUUID(string: "6E400001-B5A4-F393-E0A9-E50E24DCCA9E")
    static let nusTxUUID      = CBUUID(string: "6E400002-B5A4-F393-E0A9-E50E24DCCA9E") // write
    static let nusRxUUID      = CBUUID(string: "6E400003-B5A4-F393-E0A9-E50E24DCCA9E") // notify

    private var centralManager: CBCentralManager!

    override init() {
        super.init()
        centralManager = CBCentralManager(delegate: self, queue: .main)
    }

    // MARK: Scanning

    func startScanning() {
        guard centralManager.state == .poweredOn else { return }
        discoveredPeripherals.removeAll()
        centralManager.scanForPeripherals(withServices: nil,
                                          options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
        isScanning = true
    }

    func stopScanning() {
        centralManager.stopScan()
        isScanning = false
    }

    // MARK: Connection

    func connect(_ info: PeripheralInfo) {
        stopScanning()
        centralManager.connect(info.peripheral, options: nil)
    }

    func disconnect() {
        guard let p = connectedPeripheral else { return }
        centralManager.cancelPeripheralConnection(p)
    }

    // MARK: Sending data

    func sendText(_ text: String) {
        guard let char = selectedWriteCharacteristic,
              let peripheral = connectedPeripheral,
              let data = text.data(using: .utf8) else { return }
        write(data, to: char, peripheral: peripheral, label: "TX")
    }

    func sendHex(_ hexString: String) {
        guard let char = selectedWriteCharacteristic,
              let peripheral = connectedPeripheral else { return }
        let cleaned = hexString.replacingOccurrences(of: " ", with: "")
                                .replacingOccurrences(of: "0x", with: "")
        var bytes = [UInt8]()
        var idx = cleaned.startIndex
        while idx < cleaned.endIndex {
            let end = cleaned.index(idx, offsetBy: 2, limitedBy: cleaned.endIndex) ?? cleaned.endIndex
            if let b = UInt8(cleaned[idx..<end], radix: 16) { bytes.append(b) }
            idx = end
        }
        guard !bytes.isEmpty else { return }
        let data = Data(bytes)
        write(data, to: char, peripheral: peripheral, label: "TX")
    }

    private func write(_ data: Data, to char: CBCharacteristic, peripheral: CBPeripheral, label: String) {
        let type: CBCharacteristicWriteType = char.properties.contains(.writeWithoutResponse) ? .withoutResponse : .withResponse
        peripheral.writeValue(data, for: char, type: type)
        appendLog(direction: .sent, data: data, label: label)
    }

    // MARK: Notifications

    func setNotify(_ char: CBCharacteristic, enabled: Bool) {
        connectedPeripheral?.setNotifyValue(enabled, for: char)
    }

    // MARK: Read

    func readValue(_ char: CBCharacteristic) {
        connectedPeripheral?.readValue(for: char)
    }

    // MARK: Helpers

    private func appendLog(direction: TerminalEntry.Direction, data: Data, label: String) {
        let entry = TerminalEntry(timestamp: Date(), direction: direction, data: data, label: label)
        DispatchQueue.main.async { self.terminalLog.append(entry) }
    }

    func clearLog() { terminalLog.removeAll() }
}

// MARK: - CBCentralManagerDelegate

extension BluetoothManager: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        bluetoothState = central.state
        if central.state != .poweredOn { isScanning = false }
    }

    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any],
                        rssi RSSI: NSNumber) {
        let name = peripheral.name
            ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String
            ?? "Unknown"
        let rssi = RSSI.intValue
        if let i = discoveredPeripherals.firstIndex(where: { $0.id == peripheral.identifier }) {
            discoveredPeripherals[i].rssi = rssi
        } else {
            let info = PeripheralInfo(id: peripheral.identifier, peripheral: peripheral,
                                      name: name, rssi: rssi, isConnected: false)
            discoveredPeripherals.append(info)
        }
        discoveredPeripherals.sort { $0.rssi > $1.rssi }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        connectedPeripheral = peripheral
        peripheral.delegate = self
        peripheral.discoverServices(nil)
        if let i = discoveredPeripherals.firstIndex(where: { $0.id == peripheral.identifier }) {
            discoveredPeripherals[i].isConnected = true
        }
        appendLog(direction: .info,
                  data: "Connected to \(peripheral.name ?? peripheral.identifier.uuidString)".data(using: .utf8)!,
                  label: "info")
    }

    func centralManager(_ central: CBCentralManager,
                        didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        appendLog(direction: .info,
                  data: "Disconnected".data(using: .utf8)!,
                  label: "info")
        connectedPeripheral = nil
        services = []
        selectedWriteCharacteristic = nil
        subscribedCharacteristics = []
        if let i = discoveredPeripherals.firstIndex(where: { $0.id == peripheral.identifier }) {
            discoveredPeripherals[i].isConnected = false
        }
    }

    func centralManager(_ central: CBCentralManager,
                        didFailToConnect peripheral: CBPeripheral, error: Error?) {
        appendLog(direction: .info,
                  data: "Failed to connect: \(error?.localizedDescription ?? "unknown")".data(using: .utf8)!,
                  label: "info")
    }
}

// MARK: - CBPeripheralDelegate

extension BluetoothManager: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let svcs = peripheral.services else { return }
        services = svcs
        for svc in svcs { peripheral.discoverCharacteristics(nil, for: svc) }
    }

    func peripheral(_ peripheral: CBPeripheral,
                    didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let chars = service.characteristics else { return }
        for char in chars {
            // Auto-subscribe to notifiable characteristics
            if char.properties.contains(.notify) || char.properties.contains(.indicate) {
                peripheral.setNotifyValue(true, for: char)
                if !subscribedCharacteristics.contains(where: { $0.uuid == char.uuid }) {
                    subscribedCharacteristics.append(char)
                }
            }
            // Auto-select first writable characteristic for TX
            if selectedWriteCharacteristic == nil,
               char.properties.contains(.write) || char.properties.contains(.writeWithoutResponse) {
                selectedWriteCharacteristic = char
            }
        }
        // Re-publish so the UI sees discovered characteristics
        services = peripheral.services ?? []
    }

    func peripheral(_ peripheral: CBPeripheral,
                    didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let data = characteristic.value, !data.isEmpty else { return }
        let short = String(characteristic.uuid.uuidString.prefix(8))
        appendLog(direction: .received, data: data, label: short)
    }

    func peripheral(_ peripheral: CBPeripheral,
                    didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { }

    func peripheral(_ peripheral: CBPeripheral,
                    didWriteValueFor characteristic: CBCharacteristic, error: Error?) { }
}
