package llc.arma.ble.data.repository

import android.app.Application
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
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.data.repository.extensions.toTemperature
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

@OptIn(ExperimentalUnsignedTypes::class)
fun readThermometerHistory(
    address: String,
    app: Application,
): Flow<Result<ProgressState<List<Ble.Thermometer.HistoryPoint>>, BleException>> {

    return flow {

        var lastMeasureSystemTime: Long? = null

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

        val resultTemperaturePackage: MutableList<Float> = mutableListOf()

        var expectedDataSize: Int? = null

        if (app.checkPermission()) {

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

            try {

                val characteristic = connection.discoverServices()
                    .findService(serviceUUID)
                    ?.findCharacteristic(temperatureHistoryReadUUID)
                    ?: 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 {

                    var nextPackageDataCount = value.get2byteUIntAt(2)

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

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

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

                        val temperatureDataArray = 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)

                            value.toUByteArray().asList().subList(16, value.size)

                        } else {

                            value.toUByteArray().asList().subList(4, value.size)

                        }

                        nextPackageDataCount = value.get2byteUIntAt(2)

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

                        expectedDataSize =
                            nextPackageDataCount.toInt() + resultTemperaturePackage.size

                        emit(Result.success(ProgressState.Progress(0f / expectedDataSize.toFloat())))
                        emit(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize.toFloat())))

                        characteristic.write(DataByteArray.from(5))
                        value = characteristic.read().value

                    }

                    emit(
                        Result.success(
                            ProgressState.Finished(
                                resultTemperaturePackage.withIndex().map {
                                    Ble.Thermometer.HistoryPoint(
                                        date = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
                                        value = it.value
                                    )
                                }
                            )
                        )

                    )

                }


            } catch (err: Throwable) {

                emit(Result.failure(BleException.UnexpectedResponse))

            } finally {

                connection.close()

            }

        } else {

            emit(Result.failure(BleException.PermissionDenied))

        }

    }

}