package llc.arma.ble.data

import android.Manifest
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble

class ReadTemperatureHistoryCallback(
    private val app: Application,
    private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit
) : BluetoothGattCallback() {

    enum class Property {
        DATA_SIZE, PACKAGE
    }

    private fun ByteArray.get4byteUIntAt(idx: Int) =
        ((this[idx + 3].toUInt() and 0xFFu) shl 24) or
                ((this[idx + 2].toUInt() and 0xFFu) shl 16) or
                ((this[idx + 1].toUInt() and 0xFFu) shl 8) or
                (this[idx].toUInt() and 0xFFu)

    private fun ByteArray.get2byteUIntAt(idx: Int) =
        ((this[idx + 1].toUInt() and 0xFFu) shl 8) or
                (this[idx].toUInt() and 0xFFu)

    private var readProperty: Property? = null

    init {
        onResult(Result.success(ProgressState.Indeterminate))
    }

    override fun onConnectionStateChange(
        gatt: BluetoothGatt,
        status: Int,
        newState: Int
    ) {
        super.onConnectionStateChange(gatt, status, newState)

        if(status == BluetoothGatt.GATT_SUCCESS){

            if(newState == BluetoothGatt.STATE_CONNECTED){

                if (checkPermission()) {
                    gatt.discoverServices()

                } else {
                    onResult(Result.failure(BleException.UnexpectedResponse))
                    gatt.close()
                }
            }

        } else {

            onResult(Result.failure(BleException.UnexpectedResponse))
            gatt.close()

        }

    }

    override fun onServicesDiscovered(
        gatt: BluetoothGatt,
        status: Int
    ) {
        super.onServicesDiscovered(gatt, status)
        if(status == BluetoothGatt.GATT_SUCCESS){
            gatt.getService(serviceUUID)?.getCharacteristic(temperatureHistoryReadUUID)?.let {

                if (checkPermission()) {

                    readProperty = Property.DATA_SIZE
                    gatt.writeCharacteristic(it, byteArrayOf(2))

                } else {

                    onResult(Result.failure(BleException.PermissionDenied))
                    gatt.close()

                }

            }

        }

    }

    private var lastMeasureSystemTime: Long? = null

    private var bleMeasureInterval: Long? = null
    private var bleRealTime: Long? = null
    private var bleLastMeasureTime: Long? = null

    private val resultTemperaturePackage: MutableList<Float> = mutableListOf()

    var expectedDataSize: Int? = null

    override fun onCharacteristicRead(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        status: Int
    ) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
            super.onCharacteristicRead(gatt, characteristic, status)
            onCommonCharacteristicRead(gatt, characteristic, characteristic.value, status)
        }
    }

    override fun onCharacteristicRead(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
        status: Int
    ) {
        super.onCharacteristicRead(gatt, characteristic, value, status)
        onCommonCharacteristicRead(gatt, characteristic, value, status)
    }

    @OptIn(ExperimentalUnsignedTypes::class)
    private fun onCommonCharacteristicRead(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        value: ByteArray,
        status: Int
    ){

        if(status == BluetoothGatt.GATT_SUCCESS){
            when(readProperty){
                Property.DATA_SIZE -> {

                    if(value.contentEquals(byteArrayOf(0, 0))) {
                        onResult(
                            Result.success(
                                ProgressState.Finished(
                                    emptyList()
                                )
                            )
                        )
                        gatt.close()

                    } else {

                        val writeData = mutableListOf(
                            1.toByte(),
                            0.toByte(),
                            0.toByte()
                        ).apply {
                            addAll(value.toList())
                        }.toByteArray()

                        readProperty = Property.PACKAGE
                        gatt.writeCharacteristic(characteristic, writeData)

                    }
                }
                Property.PACKAGE -> {

                    if(value[0] == 250.toByte()){

                        bleMeasureInterval = value.get4byteUIntAt(4).toLong()
                        bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
                        bleRealTime = value.get4byteUIntAt(12).toLong()

                        lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)

                        val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)

                        resultTemperaturePackage.addAll(
                            temperatureDataArray.chunked(2).map {
                                it.toUByteArray().toTemperature()
                            }.toMutableList()
                        )

                        val nextPackageDataCount = value.get2byteUIntAt(2)
                        expectedDataSize = nextPackageDataCount.toInt() + resultTemperaturePackage.size
                        Log.d("read", expectedDataSize.toString())
                        onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
                        onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))

                        if(nextPackageDataCount != 0.toUInt()){

                            if (checkPermission()) {

                                gatt.writeCharacteristic(characteristic, byteArrayOf(5))
                                gatt.readCharacteristic(characteristic)

                            } else {

                                onResult(Result.failure(BleException.PermissionDenied))
                                gatt.close()

                            }

                        } else {
                            onResult(
                                Result.success(
                                    ProgressState.Finished(
                                        resultTemperaturePackage.withIndex().map {
                                            Ble.Thermometer.MeasurePoint(
                                                date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                value = it.value
                                            )
                                        }
                                    )
                                )
                            )
                            gatt.close()
                        }

                    } else {

                        if (value[0] == 251.toByte()) {

                            val nextPackageDataCount = value.get2byteUIntAt(2)
                            val temperatureDataArray = value.toUByteArray().toList().subList(4, value.size)

                            resultTemperaturePackage.addAll(
                                temperatureDataArray.chunked(2).map {
                                    it.toUByteArray().toTemperature()
                                }
                            )

                            onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))

                            if (nextPackageDataCount != 0.toUInt()) {

                                val writeData = byteArrayOf(5)

                                gatt.writeCharacteristic(characteristic, writeData)
                                gatt.readCharacteristic(characteristic)

                            } else {
                                onResult(
                                    Result.success(
                                        ProgressState.Finished(
                                            resultTemperaturePackage.withIndex().map {
                                                Ble.Thermometer.MeasurePoint(
                                                    date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
                                                    value = it.value
                                                )
                                            }
                                        )
                                    )
                                )
                                gatt.close()
                            }
                        } else {
                            onResult(Result.failure(BleException.UnexpectedResponse))
                            gatt.close()
                        }

                    }
                }
                else -> {
                    onResult(Result.failure(BleException.UnexpectedResponse))
                    gatt.close()

                }

            }

        }
    }

    override fun onCharacteristicWrite(
        gatt: BluetoothGatt,
        characteristic: BluetoothGattCharacteristic,
        status: Int
    ) {
        super.onCharacteristicWrite(gatt, characteristic, status)
        if(status == BluetoothGatt.GATT_SUCCESS){

            if (checkPermission()) {

                gatt.readCharacteristic(characteristic)

            } else {

                onResult(Result.failure(BleException.PermissionDenied))
                gatt.close()

            }

        } else {
            onResult(Result.failure(BleException.UnexpectedResponse))
            gatt.close()
        }

    }

    fun checkPermission(): Boolean {

        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
                    PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
                    PackageManager.PERMISSION_GRANTED
        } else {
            return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
                    PackageManager.PERMISSION_GRANTED &&
                    ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
                    PackageManager.PERMISSION_GRANTED
        }
    }

    fun BluetoothGatt.writeCharacteristic(
        characteristic: BluetoothGattCharacteristic,
        data: ByteArray
    ): Result<Unit, BleException>{

        return if(checkPermission()){

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
            }else{
                characteristic.value = data
                writeCharacteristic(characteristic)
            }

            Result.success(Unit)

        } else {
            Result.failure(BleException.PermissionDenied)
        }

    }

}