package com.joe.camera.webcam.ble

import android.annotation.SuppressLint
import android.bluetooth.*
import android.bluetooth.le.*
import android.content.Context
import android.os.ParcelUuid
import com.joe.camera.webcam.model.CameraState
import com.joe.camera.webcam.model.RemoteCommand
import java.util.UUID

@SuppressLint("MissingPermission")
class BleController(
    context: Context,
    private val onCommand: (RemoteCommand) -> Unit,
    private val onRemoteState: (CameraState) -> Unit,
    private val onMessage: (String) -> Unit,
) {
    companion object {
        val SERVICE_UUID: UUID = UUID.fromString("0000fff0-0000-1000-8000-00805F9B34FB")
        val CHARACTERISTIC_UUID: UUID = UUID.fromString("0000fff2-0000-1000-8000-00805F9B34FB")
        val CLIENT_CONFIG_UUID: UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    }
    private val manager = context.getSystemService(BluetoothManager::class.java)
    private val adapter get() = manager?.adapter
    private val appContext = context.applicationContext
    private var gattServer: BluetoothGattServer? = null
    private var serverCharacteristic: BluetoothGattCharacteristic? = null
    private val serverDevices = linkedSetOf<BluetoothDevice>()
    private var advertiser: BluetoothLeAdvertiser? = null
    private var scanner: BluetoothLeScanner? = null
    private var clientGatt: BluetoothGatt? = null
    private var clientCharacteristic: BluetoothGattCharacteristic? = null
    private var currentState = CameraState.FREE

    fun startPeripheral() {
        stopCentral()
        if (adapter?.isEnabled != true) return onMessage("蓝牙未开启")
        advertiser = adapter?.bluetoothLeAdvertiser ?: return onMessage("此设备不支持 BLE 外围广播")
        gattServer?.close()
        gattServer = manager.openGattServer(appContext, serverCallback)
        serverCharacteristic = BluetoothGattCharacteristic(
            CHARACTERISTIC_UUID,
            BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_WRITE or BluetoothGattCharacteristic.PROPERTY_NOTIFY,
            BluetoothGattCharacteristic.PERMISSION_READ or BluetoothGattCharacteristic.PERMISSION_WRITE,
        ).also { characteristic ->
            characteristic.addDescriptor(BluetoothGattDescriptor(CLIENT_CONFIG_UUID, BluetoothGattDescriptor.PERMISSION_READ or BluetoothGattDescriptor.PERMISSION_WRITE))
            val service = BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY)
            service.addCharacteristic(characteristic)
            gattServer?.addService(service)
        }
        val settings = AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY).setConnectable(true).build()
        val data = AdvertiseData.Builder().setIncludeDeviceName(false).addServiceUuid(ParcelUuid(SERVICE_UUID)).build()
        advertiser?.startAdvertising(settings, data, advertiseCallback)
    }

    fun startCentral() {
        stopPeripheral()
        if (adapter?.isEnabled != true) return onMessage("蓝牙未开启")
        scanner = adapter?.bluetoothLeScanner ?: return onMessage("BLE 扫描不可用")
        val filter = ScanFilter.Builder().setServiceUuid(ParcelUuid(SERVICE_UUID)).build()
        scanner?.startScan(listOf(filter), ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(), scanCallback)
        onMessage("设备扫描中…")
    }

    fun sendCommand(command: RemoteCommand) {
        val gatt = clientGatt ?: return startCentral()
        val characteristic = clientCharacteristic ?: return onMessage("遥控设备尚未就绪")
        characteristic.value = byteArrayOf(command.code)
        gatt.writeCharacteristic(characteristic)
    }

    fun publishState(state: CameraState) {
        currentState = state
        val characteristic = serverCharacteristic ?: return
        characteristic.value = byteArrayOf(state.wireCode)
        serverDevices.forEach { gattServer?.notifyCharacteristicChanged(it, characteristic, false) }
    }

    private val advertiseCallback = object : AdvertiseCallback() {
        override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) = onMessage("BLE 相机服务已启动")
        override fun onStartFailure(errorCode: Int) = onMessage("BLE 广播失败：$errorCode")
    }
    private val serverCallback = object : BluetoothGattServerCallback() {
        override fun onConnectionStateChange(device: BluetoothDevice, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) serverDevices += device else serverDevices -= device
        }
        override fun onCharacteristicReadRequest(device: BluetoothDevice, requestId: Int, offset: Int, characteristic: BluetoothGattCharacteristic) {
            gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, byteArrayOf(currentState.wireCode))
        }
        override fun onCharacteristicWriteRequest(device: BluetoothDevice, requestId: Int, characteristic: BluetoothGattCharacteristic, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
            value.firstOrNull()?.let { RemoteCommand.fromCode(it)?.let(onCommand) }
            if (responseNeeded) gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
        }
        override fun onDescriptorWriteRequest(device: BluetoothDevice, requestId: Int, descriptor: BluetoothGattDescriptor, preparedWrite: Boolean, responseNeeded: Boolean, offset: Int, value: ByteArray) {
            if (responseNeeded) gattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null)
        }
    }
    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            scanner?.stopScan(this)
            clientGatt?.close()
            clientGatt = result.device.connectGatt(appContext, false, clientCallback, BluetoothDevice.TRANSPORT_LE)
        }
        override fun onScanFailed(errorCode: Int) = onMessage("BLE 扫描失败：$errorCode")
    }
    private val clientCallback = object : BluetoothGattCallback() {
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) { onMessage("已连接：${gatt.device.name ?: gatt.device.address}"); gatt.discoverServices() }
            else { clientCharacteristic = null; onRemoteState(CameraState.FREE); onMessage("遥控连接已断开") }
        }
        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            clientCharacteristic = gatt.getService(SERVICE_UUID)?.getCharacteristic(CHARACTERISTIC_UUID)
            val characteristic = clientCharacteristic ?: return onMessage("远端服务不兼容")
            gatt.setCharacteristicNotification(characteristic, true)
            characteristic.getDescriptor(CLIENT_CONFIG_UUID)?.let { descriptor ->
                descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                gatt.writeDescriptor(descriptor)
            }
        }
        override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) clientCharacteristic?.let(gatt::readCharacteristic)
        }
        @Deprecated("Deprecated in API 33")
        override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) { characteristic.value?.firstOrNull()?.let { onRemoteState(CameraState.fromWire(it)) } }
        @Deprecated("Deprecated in API 33")
        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { characteristic.value?.firstOrNull()?.let { onRemoteState(CameraState.fromWire(it)) } }
        override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) { value.firstOrNull()?.let { onRemoteState(CameraState.fromWire(it)) } }
        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) { value.firstOrNull()?.let { onRemoteState(CameraState.fromWire(it)) } }
    }

    fun stopPeripheral() { runCatching { advertiser?.stopAdvertising(advertiseCallback) }; advertiser = null; serverDevices.clear(); gattServer?.close(); gattServer = null }
    fun stopCentral() { runCatching { scanner?.stopScan(scanCallback) }; scanner = null; clientGatt?.disconnect(); clientGatt?.close(); clientGatt = null; clientCharacteristic = null }
    fun release() { stopCentral(); stopPeripheral() }
}
