package llc.arma.ble.data.repository

import android.Manifest
import android.annotation.SuppressLint
import android.app.Application
import android.content.pm.PackageManager
import android.util.Log
import androidx.annotation.RequiresPermission
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import llc.arma.ble.data.repository.extensions.checkPermission
import llc.arma.ble.data.repository.extensions.get2byteUIntAt
import llc.arma.ble.data.repository.extensions.get4byteUIntAt
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
import no.nordicsemi.android.common.core.DataByteArray
import no.nordicsemi.android.kotlin.ble.client.main.callback.ClientBleGatt
import no.nordicsemi.android.kotlin.ble.client.main.service.ClientBleGattCharacteristic
import java.nio.ByteBuffer
import java.util.BitSet
import java.util.Locale

@RequiresPermission(allOf = ["android.permission.BLUETOOTH_CONNECT"])
suspend fun readTable(
    characteristic: ClientBleGattCharacteristic,
    startRequest: ByteArray,
    nextRequestPayload: ByteArray
): List<Byte> {

    characteristic.write(DataByteArray(startRequest))
    var value = characteristic.read().value
    val tableResult = mutableListOf<Byte>()

    do {

        val nextPackageDataCount = value.get2byteUIntAt(2)

        tableResult.addAll(value.asList().subList(4, value.size))

        characteristic.write(DataByteArray(nextRequestPayload))
        value = characteristic.read().value

    } while (nextPackageDataCount.toInt() != 0)

    return tableResult

}

@OptIn(ExperimentalStdlibApi::class)
fun readHostHistory(
    address: String,
    app: Application,
): Flow<Result<ProgressState<List<Ble.Host.HistoryPoint>>, BleException>> {

    return flow {

        if (app.checkPermission()) {

            val connection =
                ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))

            try {

                val characteristic = connection.discoverServices()
                    .findService(serviceUUID)
                    ?.findCharacteristic(hostHistoryReadUUID)
                    ?: throw IllegalStateException()

                characteristic.write(DataByteArray.from(2))

                var value = characteristic.read().value

                if (value.contentEquals(byteArrayOf(0, 0))) {

                    emit(Result.success(ProgressState.Finished(emptyList())))

                } else {

                    val firstTablePackage: MutableList<Byte> = mutableListOf()
                    val secondTablePackage: MutableList<Byte> = mutableListOf()

                    var tableSize = value.get2byteUIntAt(0)

                    firstTablePackage.addAll(
                        readTable(
                            characteristic,
                            //Чтение без удаления
                            byteArrayOf(1, 0, 0, -2, -1),
                            byteArrayOf(5)
                        )
                    )

                    Log.d("table", firstTablePackage.joinToString { it.toString() })

                    val bleMeasureInterval = firstTablePackage.toByteArray().get4byteUIntAt(0).toLong()
                    val bleLastMeasureTime = firstTablePackage.toByteArray().get4byteUIntAt(4).toLong()
                    val bleRealTime = firstTablePackage.toByteArray().get4byteUIntAt(8).toLong()
                    val lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime - bleLastMeasureTime) * 1_000)

                    secondTablePackage.addAll(
                        readTable(characteristic, byteArrayOf(6), byteArrayOf(6))
                    )

                    fun getBleIdIndex(bytes: ByteArray): UInt{

                        val bits = BitSet.valueOf(bytes)
                        Log.d("bits", bits.toByteArray().joinToString())
                        bits.clear(12, 16)

                        val arr = bits.toByteArray()

                        if(arr.isEmpty()){
                            return 0x00.toUInt()
                        }

                        if(arr.size == 1){
                            return arr[0].toUInt()
                        }

                        return arr.get2byteUIntAt(0)

                    }

                    fun getInnerIndex(byte: Byte): Int{

                        var bits = BitSet.valueOf(byteArrayOf(byte))
                        bits.clear(0, 4)
                        bits = bits.get(4, 8)
                        val arr = bits.toByteArray()

                        if(arr.isEmpty()){
                            return 0x00
                        }

                        return bits.toByteArray()[0].toInt()

                    }

                    fun getDevType(byte: Byte): Int{

                        val bits = BitSet.valueOf(byteArrayOf(byte))
                        bits.clear(5, 9)
                        val arr = bits.toByteArray()

                        if(arr.isEmpty()){
                            return 0x00
                        }

                        return bits.toByteArray()[0].toInt()

                    }

                    fun getDevDataSize(byte: Byte): Int{

                        var bits = BitSet.valueOf(byteArrayOf(byte))
                        bits.clear(0, 5)
                        bits = bits.get(5, 8)
                        val arr = bits.toByteArray()

                        if(arr.isEmpty()){
                            return 0x00
                        }

                        return bits.toByteArray()[0].toInt()

                    }

                    var bleTableOffset = 12
                    var periods = mutableListOf<Pair<Boolean, List<String>>>()

                    var periodBle = mutableListOf<String>()

                    do {

                        val bleIdTableCell =
                            firstTablePackage.drop(bleTableOffset).take(2).toByteArray()

                        val intervalEnd = "ff0f"
                        val intervalEndAndHit = "fe0f"

                        if (bleIdTableCell.contentEquals(intervalEnd.hexToByteArray())) {

                            bleTableOffset += 2
                            if(periodBle.isEmpty()) bleTableOffset += 2
                            periods.add(Pair(false, periodBle))
                            periodBle = mutableListOf()

                            continue

                        }

                        if (bleIdTableCell.contentEquals(intervalEndAndHit.hexToByteArray())) {
                            bleTableOffset += 2
                            if(periodBle.isEmpty()) bleTableOffset += 2
                            periods.add(Pair(true, periodBle))
                            periodBle = mutableListOf()

                            continue

                        }

                        val innerIndex = getInnerIndex(bleIdTableCell[1])

                        val bleTableIndex = getBleIdIndex(bleIdTableCell) * 8u

                        val serial =
                            secondTablePackage.drop(bleTableIndex.toInt()).take(6).reversed()
                                .joinToString(
                                    separator = ":",
                                    transform = { it.toHexString().padStart(2, '0') })
                                .uppercase(Locale.getDefault())
                        val devTypeByte = secondTablePackage.drop(bleTableIndex.toInt() + 6)[0]

                        val devType = getDevType(devTypeByte)
                        val devDataSize = getDevDataSize(devTypeByte)
                        bleTableOffset += 2

                        if(innerIndex == 0)
                            bleTableOffset += 2

                        if (devDataSize != 0) {
                            val payload = getBleIdIndex(
                                firstTablePackage.drop(bleTableOffset).take(devDataSize)
                                    .toByteArray()
                            )
                            bleTableOffset += devDataSize
                        }

                        periodBle.add(serial)

                    } while (bleTableOffset < firstTablePackage.size)

                    emit(
                        Result.success(
                            ProgressState.Finished(
                                periods.withIndex().map {
                                    Ble.Host.HistoryPoint(
                                        date = lastMeasureSystemTime - (((periods.size - 1) - it.index) * bleMeasureInterval),
                                        hit = it.value.first,
                                        value = it.value.second
                                    )
                                }
                            )
                        )

                    )

                }


            } catch (err: Throwable) {
                err.printStackTrace()
                emit(Result.failure(BleException.UnexpectedResponse))

            } finally {

                connection.close()

            }

        } else {

            emit(Result.failure(BleException.PermissionDenied))

        }

    }

}

@OptIn(ExperimentalStdlibApi::class)
suspend fun readHostBleTable(
    address: String,
    app: Application,
): Result<List<String>, BleException> {

    return if (app.checkPermission()) {

        val connection =
            ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))

        try {

            val characteristic = connection.discoverServices()
                .findService(serviceUUID)
                ?.findCharacteristic(hostHistoryReadUUID)
                ?: throw IllegalStateException()

            characteristic.write(DataByteArray.from(7))

            var value = characteristic.read().value

            if (value.contentEquals(byteArrayOf(0, 0))) {

                Result.success(emptyList())

            } else {

                var tableSize = value.get2byteUIntAt(0)

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

                characteristic.write(DataByteArray(writeData))
                value = characteristic.read().value

                Result.success(
                    readTable(characteristic, byteArrayOf(6), byteArrayOf(6)).chunked(8).map {
                        it.take(6)
                            .reversed()
                            .joinToString(
                                separator = ":",
                                transform = { it.toHexString().padStart(2, '0') })
                            .uppercase(Locale.getDefault())
                    }
                )

            }


        } catch (err: Throwable) {

            Result.failure(BleException.UnexpectedResponse)

        } finally {

            connection.close()

        }

    } else {

        Result.failure(BleException.PermissionDenied)

    }

}

@OptIn(ExperimentalStdlibApi::class)
suspend fun editBleHostTable(
    address: String,
    addBleAddress: List<String>,
    app: Application,
): Result<Int, BleException> {

    return if (app.checkPermission()) {

        val connection =
            ClientBleGatt.connect(app, address, CoroutineScope(Dispatchers.Default))

        try {

            val characteristic = connection.discoverServices()
                .findService(serviceUUID)
                ?.findCharacteristic(flashWriteUUID)
                ?: throw IllegalStateException()

            characteristic.write(DataByteArray.from(12, 1))

            Log.i("ScanRecord", "write")

            val writeCount = addBleAddress.chunked(20).sumOf { bleAddressBatch ->

                val countPayload = ByteBuffer.allocate(2).putShort(bleAddressBatch.size.toShort()).array().reversed().toByteArray()

                val command = "0b00".hexToByteArray()

                val serialPayload = bleAddressBatch.flatMap {
                    it.replace(":", "").lowercase(Locale.CANADA).hexToByteArray().reversed().toList()
                }.toByteArray()

                characteristic.write(DataByteArray.from(*command, *countPayload, *serialPayload))
                characteristic.read().value.get2byteUIntAt(0).toInt()

            }

            characteristic.write(
                DataByteArray.from(9)
            )

            delay(10_000)

            Result.success(writeCount)

        } catch (err: Throwable) {

            err.printStackTrace()

            Result.failure(BleException.UnexpectedResponse)

        } finally {

            connection.close()

        }

    } else {

        Result.failure(BleException.PermissionDenied)

    }

}