package com.iscs.bozzys.utils.ble

import android.annotation.SuppressLint
import android.app.Application
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.BluetoothStatusCodes
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.os.Build
import android.os.SystemClock
import com.iscs.bozzys.utils.LogUtil
import com.iscs.bozzys.utils.byteArrayToHexString
import com.iscs.bozzys.utils.hexToByteArray
import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap

/**
 * 蓝牙连接管理器
 */
class BleManager(
    val app: Application,
    val mac: String = "",
    val mtu: Int = 500,
    val needIndicate: Boolean = true,
    val writeUUID: String = BleProtocol.INDICATE_UUID,
    val serviceUUID: String = BleProtocol.SERVICE_UUID,
) {

    companion object {
        private const val TAG = "BLEManager"
    }

    // 蓝牙管理器
    private val bm = app.getSystemService(BluetoothManager::class.java)

    // 蓝牙适配器
    private val ba = bm.adapter

    // 当前连接的设备
    private var device: BluetoothDevice? = null

    // gatt连接对象
    private var gatt: BluetoothGatt? = null

    // 连接回调
    private var doneConnect: CancellableContinuation<BleConnectResult>? = null

    // 待响应式的请求数据接收池
    private val receiverPool = ConcurrentHashMap<String, CompletableDeferred<BleFrame>>()

    // 蓝牙扫描回调
    private val scanCallback = object : ScanCallback() {

        override fun onScanResult(callbackType: Int, result: ScanResult?) {
            super.onScanResult(callbackType, result)
            LogUtil.i(TAG, "scan() onScanResult() ${result?.device?.address}")
            if (result != null && result.device != null) {
                device = result.device
                // 查找到设备后，停止扫描并且执行连接操作
                stopScan()
                // 执行连接蓝牙设备
                innerConnect()
            }
        }

        override fun onScanFailed(errorCode: Int) {
            super.onScanFailed(errorCode)
            LogUtil.i(TAG, "scan() onScanFailed() $errorCode")
            // 扫描失败重新执行扫描
            scan()
        }

    }

    /**
     * 蓝牙Gatt连接回调
     */
    @OptIn(ExperimentalCoroutinesApi::class)
    private val gattCallback = object : BluetoothGattCallback() {

        @SuppressLint("MissingPermission")
        override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                LogUtil.i(TAG, "gattCallback onConnectionStateChange() gatt connected")
                gatt.discoverServices()
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                LogUtil.i(TAG, "gattCallback onConnectionStateChange() gatt disconnected")
                if (!(this@BleManager.doneConnect?.isCompleted ?: false)) {
                    this@BleManager.doneConnect?.resume(BleConnectResult(false), null)
                }
            }
        }

        @SuppressLint("MissingPermission")
        override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                LogUtil.i(TAG, "gattCallback onServicesDiscovered() find services success")
                // 执行设置mtu操作
                gatt.requestMtu(mtu)
            }
        }

        override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
            super.onMtuChanged(gatt, mtu, status)
            LogUtil.i(TAG, "gattCallback onMtuChanged() mtu $mtu")
            Thread {
                val indicateOk = enableIndicate()
                SystemClock.sleep(1000)
                if (needIndicate) {
                    if (!(this@BleManager.doneConnect?.isCompleted ?: false)) this@BleManager.doneConnect?.resume(BleConnectResult(indicateOk), null)
                } else {
                    if (!(this@BleManager.doneConnect?.isCompleted ?: false)) this@BleManager.doneConnect?.resume(BleConnectResult(true), null)
                }
            }.start()
        }

        @SuppressLint("MissingPermission")
        override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
            super.onCharacteristicRead(gatt, characteristic, value, status)
            LogUtil.i(TAG, "gattCallback onCharacteristicRead()")
        }

        override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
            super.onCharacteristicWrite(gatt, characteristic, status)
            if (characteristic.value == null) return
            LogUtil.w(TAG, "gattCallback ---> ${characteristic.value.byteArrayToHexString(" ")}")
        }

        override fun onDescriptorRead(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
            super.onDescriptorRead(gatt, descriptor, status, value)
            LogUtil.i(TAG, "gattCallback onDescriptorRead() data write success")
        }

        override fun onDescriptorRead(gatt: BluetoothGatt?, descriptor: BluetoothGattDescriptor?, status: Int) {
            super.onDescriptorRead(gatt, descriptor, status)
            LogUtil.i(TAG, "gattCallback onDescriptorRead() data write success")
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?) {
            super.onCharacteristicChanged(gatt, characteristic)
            LogUtil.w(TAG, "gattCallback <--- ${characteristic?.value?.byteArrayToHexString(" ")}")
            val data = characteristic?.value ?: byteArrayOf()
            var key = ""
            receiverPool.forEach { item ->
                if (data.byteArrayToHexString().startsWith(item.key.split("_")[1])) {
                    // 找到指定响应体
                    key = item.key
                    return@forEach
                }
            }
            if (key.isNotEmpty()) {
                val deferred = receiverPool.remove(key)
                if (deferred != null && !deferred.isCompleted) {
                    val spl = key.split("_")
                    val rspCodeLen = spl[1].length / 2
                    deferred.complete(BleFrame(spl[0].hexToByteArray(), data.copyOfRange(rspCodeLen, data.size), spl[1].hexToByteArray()))
                }
            }
        }

        override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, value: ByteArray) {
            super.onCharacteristicChanged(gatt, characteristic, value)
            LogUtil.w(TAG, "gattCallback <--- ${value.byteArrayToHexString(" ")}")
            var key = ""
            receiverPool.forEach { item ->
                if (value.byteArrayToHexString().startsWith(item.key.split("_")[1])) {
                    // 找到指定响应体
                    key = item.key
                    return@forEach
                }
            }
            if (key.isNotEmpty()) {
                val deferred = receiverPool.remove(key)
                if (deferred != null && !deferred.isCompleted) {
                    val spl = key.split("_")
                    val rspCodeLen = spl[1].length / 2
                    deferred.complete(BleFrame(spl[0].hexToByteArray(), value.copyOfRange(rspCodeLen, value.size), spl[1].hexToByteArray()))
                }
            }
        }
    }

    /**
     * 协程方式的连接
     */
    suspend fun connect(): BleConnectResult = suspendCancellableCoroutine { block ->
        this.doneConnect = block
        // 开始执行扫描连接
        scan()
    }

    /**
     * 查找设备
     */
    @SuppressLint("MissingPermission")
    private fun scan() {
        if (ba == null || !ba.isEnabled) {
            // 蓝牙不可用
            LogUtil.e(TAG, "scan() bluetooth unused")
            if (!(this.doneConnect?.isCompleted ?: false)) this.doneConnect?.resume(BleConnectResult(false), null)
            return
        }
        LogUtil.i(TAG, "scan() bluetooth started")
        // 获取扫描对象
        val scan = ba.bluetoothLeScanner
        // 设置扫描过滤
        val filters = ArrayList<ScanFilter>()
        if (mac.isNotEmpty()) filters.add(ScanFilter.Builder().setDeviceAddress(mac).build())
        // 设置蓝牙扫描频率高 SCAN_MODE_LOW_LATENCY
        // 设置蓝牙扫描频率中 SCAN_MODE_BALANCED
        val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
        scan.startScan(filters, settings, scanCallback)
    }

    @SuppressLint("MissingPermission")
    fun stopScan() {
        try {
            ba.bluetoothLeScanner.stopScan(scanCallback)
        } catch (e: Exception) {
            LogUtil.e(TAG, "stopScan() stop scan failed $e")
        }
    }

    /**
     * 连接设备，内部方法
     */
    @SuppressLint("MissingPermission")
    private fun innerConnect() {
        // context -> app
        // autoConnect -> false 避免设备连接异常
        try {
            gatt = device?.connectGatt(app, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
        } catch (e: Exception) {
            LogUtil.e(TAG, "connect() connect to device ${device?.address} failed $e")
        }
    }

    /**
     * 断开连接
     */
    @SuppressLint("MissingPermission")
    fun disconnect() {
        device?.let {
            gatt?.disconnect()
            gatt?.close()
        }
    }

    /**
     * 是否使能Indicate
     */
    @SuppressLint("MissingPermission")
    private fun enableIndicate(uuid: String = writeUUID): Boolean {
        gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(UUID.fromString(uuid))?.let {
            // 开启通知
            val open = gatt?.setCharacteristicNotification(it, true)
            LogUtil.i(TAG, "enableIndicate() open notification $open")
            val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
            LogUtil.i(TAG, "enableIndicate() get descriptor $descriptor")
            if (open != true || descriptor == null) return false

            // 写入desc
            val enable = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                gatt?.writeDescriptor(descriptor, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE) == BluetoothStatusCodes.SUCCESS
            } else {
                descriptor.value = BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
                gatt?.writeDescriptor(descriptor)
            }
            LogUtil.i(TAG, "enableIndicate() indicate enable $enable")
            return enable == true
        }
        return false
    }

    /**
     * 写数据，带响应
     */
    @SuppressLint("MissingPermission")
    suspend fun writeByResponse(frame: BleFrame) = withContext(Dispatchers.IO) {
        val deferred = CompletableDeferred<BleFrame>()
        val writeUUID = if (frame.writeUUID.isNotEmpty()) UUID.fromString(frame.writeUUID) else UUID.fromString(writeUUID)
        receiverPool["${frame.reqCode.byteArrayToHexString()}_${frame.rspCode.byteArrayToHexString()}"] = deferred
        // 发送数据方法兼容处理
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(writeUUID)?.let {
                gatt?.writeCharacteristic(it, frame.reqCode + frame.data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
            }
        } else {
            val characteristic = gatt?.getService(UUID.fromString(serviceUUID))?.getCharacteristic(writeUUID)
            characteristic?.value = frame.reqCode + frame.data
            characteristic?.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
            gatt?.writeCharacteristic(characteristic)
        }
        try {
            withTimeout(1000) { deferred.await() }
        } catch (_: TimeoutCancellationException) {
            BleFrame(byteArrayOf(), byteArrayOf(), byteArrayOf())
        }
    }

}