package com.mytatvarnsdk

import android.app.Application
import android.bluetooth.BluetoothAdapter
import android.content.Intent
import android.content.Context
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import cgmblelib.Enum.enumError
import cgmblelib.base.BApplication
import cgmblelib.database.entity.PocDevice
import cgmblelib.database.entity.PocGlucose
import cgmblelib.database.repository.RepositoryDevice
import cgmblelib.utils.SharedPreferencesLibraryUtil
import cgmblelib.utils.SyncMeta
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.mytatvarnsdk.activity.HelpActivity
import com.mytatvarnsdk.activity.StartCGMActivity
import com.mytatvarnsdk.model.AllCGMLogRequest
import com.mytatvarnsdk.model.CgmLog
import com.mytatvarnsdk.model.CgmSensorResponse
import com.mytatvarnsdk.model.GlucoseLog
import com.mytatvarnsdk.model.GlucoseLog.ErrorObject
import com.mytatvarnsdk.model.GlucoseLog.GlucoseStatusObject
import com.mytatvarnsdk.model.GlucoseLog.TrendObject
import com.mytatvarnsdk.model.GlucoseLogRequest
import com.mytatvarnsdk.model.MainActivityModel
import com.mytatvarnsdk.network.AuthenticateSDKService
import com.mytatvarnsdk.network.AuthenticateSDKService.LoaderListener
import com.mytatvarnsdk.utils.DeviceStatus
import com.mytatvarnsdk.utils.TATVA_ENVIRONMENT
import io.sentry.Sentry
import io.sentry.SentryLevel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.json.JSONObject
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.Timer
import java.util.TimerTask
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

@ReactModule(name = "CgmTrackyLib")
class CgmTrackyLibModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {
    private val mModel: MainActivityModel
    var authenticateSDKService: AuthenticateSDKService
    private val job = Job()
    private val scope = CoroutineScope(Dispatchers.IO + job)
    var prefsHelper: SharedPreferencesLibraryUtil? = null
    private val apiScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
    private var debounceJob: Job? = null
    private val isBatchProcessing = AtomicBoolean(false)
    private var lastDeviceStatus: String? = null
    private var glucoseObserver: Observer<PocGlucose?>? = null
    private var deviceObserver: Observer<PocDevice?>? = null
    private var isObserving = false
    private val processedGlucoseIds = ConcurrentHashMap.newKeySet<Int>()
    private val pendingDataQueue = ConcurrentLinkedQueue<PocGlucose>()
    private val metadataLock = Mutex()
    private var uploadJob: Job? = null
    private var currentUserData: ReadableMap? = null

    init {
        mReactContext = reactContext
        prefsHelper = SharedPreferencesLibraryUtil(mReactContext)
        val viewModelStore = ViewModelStore()
        val factory =
            ViewModelProvider.AndroidViewModelFactory.getInstance(reactContext.applicationContext as Application)
        mModel = ViewModelProvider(viewModelStore, factory)[MainActivityModel::class.java]
        authenticateSDKService = AuthenticateSDKService(scope = apiScope)
    }

    companion object {
        var mReactContext: ReactApplicationContext? = null
        var userToken: String = ""
        var env: String = ""
        var lastConnectCgm: String = ""

        // Sentry event message field is typically much smaller than "extra" fields.
        // Keep JSON in extras as a full (clipped) payload, but also include a small prefix in the message
        // so it's visible in the Sentry dashboard list/search.
        private const val MAX_SENTRY_DATA_EXTRA_CHARS = 16_384
        private const val MAX_SENTRY_MESSAGE_DATA_CHARS = 2_000
    }

    override fun getName(): String {
        return "CgmTrackyLib"
    }

    /**
     * Uses the host app's Sentry client (never call Sentry.init here).
     * [data]: [Throwable] → exception event; anything else → message + extra `data` (stringified, clipped);
     * null → error message with location only.
     */
    private fun captureModuleException(
        where: String,
        data: Any?,
        level: SentryLevel = SentryLevel.ERROR
    ) {
        try {
            Sentry.withScope { scope ->
                scope.setTag("cgm_tracky_lib", "true")
                scope.setExtra("location", where)
                if (env.isNotEmpty()) {
                    scope.setExtra("tatva_env", env)
                }

                when (data) {
                    null ->
                        run {
                            Sentry.captureMessage("CgmTrackyLib: $where", level)
                        }

                    is Throwable ->
                        run {
                            // Put exception summary into `extra.data` for quick inspection.
                            val throwableText =
                                "${data.javaClass.simpleName}: ${data.message ?: ""}".trim()
                            val clipped =
                                if (throwableText.length > MAX_SENTRY_DATA_EXTRA_CHARS) {
                                    throwableText.take(MAX_SENTRY_DATA_EXTRA_CHARS) + "…"
                                } else {
                                    throwableText
                                }
                            scope.setExtra("data", clipped)
                            Sentry.captureException(data)
                        }

                    else -> {
                        val text = when (data) {
                            is String -> data
                            else -> data.toString()
                        }
                        val clipped =
                            if (text.length > MAX_SENTRY_DATA_EXTRA_CHARS) {
                                text.take(MAX_SENTRY_DATA_EXTRA_CHARS) + "…"
                            } else {
                                text
                            }
                        scope.setExtra("data", clipped)
                        // Duplicate under a more explicit key so it is easy to find in Sentry UI.
                        scope.setExtra("json", clipped)

                        // Also include a prefix in the message itself so it appears in the main event row.
                        // (In some Sentry views, extra fields are not shown by default.)
                        val messageData =
                            if (clipped.length > MAX_SENTRY_MESSAGE_DATA_CHARS) {
                                clipped.take(MAX_SENTRY_MESSAGE_DATA_CHARS) + "…"
                            } else {
                                clipped
                            }

                        Sentry.captureMessage("CgmTrackyLib: $where | data=$messageData", level)
                    }
                }
            }
        } catch (sentryEx: Exception) {
            // If Sentry itself fails, never crash the CGM processing flow.
            Log.e("captureModuleException", "Sentry capture failed: ${sentryEx.message}", sentryEx)
        }
    }

    private fun updateUserData(data: ReadableMap?) {
        currentUserData = data
        // Always reset so stale values from a previous user don't persist
        lastConnectCgm = ""
        try {
            if (data != null && data.hasKey("patient_meta_info")) {
                val patientMetaInfo = data.getMap("patient_meta_info")
                if (patientMetaInfo != null && patientMetaInfo.hasKey("last_connect_cgm")) {
                    lastConnectCgm = patientMetaInfo.getString("last_connect_cgm") ?: ""
                    Log.d("updateUserData", "lastConnectCgm: $lastConnectCgm")
                }
            }
        } catch (e: Exception) {
            Log.e("updateUserData", "Error extracting last_connect_cgm: ${e.message}", e)
            captureModuleException("updateUserData", e)
        }
    }

    @ReactMethod
    fun observeDeviceStatus(token: String, envType: String) {
        try {
            userToken = token
            env = envType

            lastDeviceStatus = null
            debounceJob?.cancel()

            Handler(Looper.getMainLooper()).post {
                // Remove any previously registered observer to prevent accumulation
                // across multiple calls (e.g. each app relaunch calls this again)
                deviceObserver?.let { mModel.device.removeObserver(it) }

                deviceObserver = Observer { device ->
                    if (device != null) {
                        val sensorId = device.qrMessage?.takeIf { it.isNotEmpty() }
                            ?: prefsHelper?.qrInformation?.sensor
                        if (!sensorId.isNullOrEmpty()) {
                            postEventDataToAPI(device, "", sensorId, envType)
                        } else {
                            Log.w("observeDeviceStatus", "sensorId is null/empty, skipping API call")
                        }
                        Log.d("observeDeviceStatus: ", device.toString())
                    }
                }
                deviceObserver?.let { mModel.device.observeForever(it) }
            }
        } catch (e: Exception) {
            Log.e("observeDeviceStatus", "observeDeviceStatus: ${e.message}", e)
            captureModuleException("observeDeviceStatus", e)
        }
    }

    @ReactMethod
    fun observeTransmitterUnbindStatus(
        token: String,
        apiResponse: String?,
        patientId: String,
        envType: String,
        userData: ReadableMap? = null
    ) {
        try {
            updateUserData(userData)
            if (apiResponse != null && apiResponse.isNotEmpty()) {
                env = envType
                userToken = token

                /*authenticateSDKService.getCGMData(
                  environment = TATVA_ENVIRONMENT.PROD,
                  token = userToken,
                  responseListener = object : AuthenticateSDKService.ResponseListener {
                    override fun onResponseSuccess(response: String) {

                      val response = Gson().fromJson(response, CgmSensorResponse::class.java)
                      val sensor = response.data?.firstOrNull()

                      if (sensor != null && !sensor.startDate.isNullOrEmpty() && !sensor.endDate.isNullOrEmpty()) {
                        val startDate = sensor.startDate
                        val endDate = sensor.endDate
                        val sensorId = sensor.sensorId

                        println("Start Date: $startDate")
                        println("End Date: $endDate")

                        if (isCurrentDateInRange(startDate, endDate)) {

                          println("Current date is in range")

                          val pocDevice =
                            RepositoryDevice.getInstance(BApplication.getContext()).latestDeviceIoThread

                          if (pocDevice != null) {
                            Log.d("pocDevice logsss", pocDevice.toString())

                            if (pocDevice.isUnBind) {
                              postEventDataToAPI(
                                pocDevice,
                                DeviceStatus.TRANSMITTER_DISCONNECT.id,
                                pocDevice.qrMessage
                              )
                            }
                          } else {
                            Log.d("pocDevice logsss", "Data null")

                            postEventDataToAPI(
                              pocDevice,
                              DeviceStatus.TRANSMITTER_DISCONNECT.id,
                              sensorId
                            )
                          }

                        } else {
                          println("Current date is out of range")
                        }
                      } else {
                        println("Start or End date not available")
                      }
                    }

                    override fun onResponseFail() {
                    }
                  }
                )*/
                val response = Gson().fromJson(apiResponse, CgmSensorResponse::class.java)
                val sensor = response.data?.firstOrNull()
                Log.d("delete1111", "startingggg")

                if (sensor != null && !sensor.startDate.isNullOrEmpty() && !sensor.endDate.isNullOrEmpty()) {
                    val startDate = sensor.startDate
                    val endDate = sensor.endDate
                    val sensorId = sensor.sensorId
                    /*     val currentPatientId = patientId
                         val lastPatientId = prefsHelper?.lastPatientId
                         val lastSensorId = prefsHelper?.qrInformation?.sensor */


                    /* if (lastPatientId != null && lastSensorId != null && lastPatientId.isNotEmpty() && lastSensorId.isNotEmpty() && (lastPatientId != currentPatientId || lastSensorId != sensorId)) {
                         Log.d("delete1111", "deleting database");
                         mModel.clearAllGlucoseAndDeviceData()
                         return
                     } */

                    if (isCurrentDateInRange(startDate, endDate)) {
                        println("Current date is in range")

                        val pocDevice =
                            RepositoryDevice.getInstance(BApplication.getContext()).latestDeviceIoThread

                        if (pocDevice != null) {
                            if (pocDevice.isUnBind) {
                                postEventDataToAPI(
                                    pocDevice,
                                    DeviceStatus.TRANSMITTER_DISCONNECT.id,
                                    pocDevice.qrMessage, envType
                                )
                            } else {
                                /* prefsHelper?.lastPatientId = currentPatientId
                                 prefsHelper?.qrInformation?.sensor = sensorId
                                 */
                            }
                        } else {
                            postEventDataToAPI(
                                pocDevice,
                                DeviceStatus.TRANSMITTER_DISCONNECT.id,
                                sensorId, envType
                            )
                        }

                    } else {
                        println("Current date is out of range")
                    }
                } else {
                    println("Start or End date not available")
                }
            }
        } catch (e: Exception) {
            Log.e("observeTransmitterUnbindStatus", "observeTransmitterUnbindStatus: ${e.message}", e)
            captureModuleException("observeTransmitterUnbindStatus", e)
        }
    }

    private var debounceDeviceTimer: Timer? = null
    private var isDebounceTimerActive: Boolean = false


    private fun postEventDataToAPI(
        device: PocDevice?,
        mStatus: String,
        sensorId: String?,
        envType: String
    ) {
        apiScope.launch {
            try {
                val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
                val bleStatus = bluetoothAdapter?.isEnabled == true

                val status: String = mStatus.ifEmpty {
                    when {
                        device?.isBoundAndConnect == true -> DeviceStatus.CONNECTED.id
                        !bleStatus -> DeviceStatus.BLUETOOTH_OFF.id
                        device?.isUnBind == true -> DeviceStatus.TRANSMITTER_DISCONNECT.id
                        device?.isBoundButDisConnect == true -> DeviceStatus.DISCONNECTED.id
                        else -> ""
                    }
                }

                Log.d("Device event Status", "Device event Status: $status")

                if (status == DeviceStatus.DISCONNECTED.id && !isDebounceTimerActive) {
                    // Start debounce timer for 1 minutes
                    debounceDeviceTimer = Timer()
                    isDebounceTimerActive = true

                    debounceDeviceTimer?.schedule(object : TimerTask() {
                        override fun run() {
                            isDebounceTimerActive = false
                            callEventAPI(device, status, sensorId, envType)
                        }
                    }, 1 * 60 * 1000) // 1 minutes in milliseconds

                    Log.d("Debounce", "Timer started for disconnection reminder")
                } else {
                    // Either device is not disconnected or timer already running
                    debounceDeviceTimer?.cancel()
                    debounceDeviceTimer = null
                    isDebounceTimerActive = false

                    Log.d("Debounce", "Timer cancelled, event triggered immediately")

                    callEventAPI(device, status, sensorId, envType)
                }

            } catch (e: Exception) {
                Log.e("postEventDataToAPI", "postEventDataToAPI: ${e.message}", e)
                captureModuleException("postEventDataToAPI", e)
            }
        }
    }

    private fun callEventAPI(
        device: PocDevice?,
        status: String,
        sensorId: String?,
        envType: String
    ) {

        Log.d("sensor id====", sensorId.toString())
        Log.d("Device event Status", "Last Device event lastDeviceStatus API: $lastDeviceStatus")

        if (status.isNotEmpty() && status != lastDeviceStatus) {
            lastDeviceStatus = status

            Log.d("Device event Status", "Device event Status API: $status")

            val rawData = JSONObject().apply {
                put("transmitterName", device?.name ?: "")
                put("SensorId", sensorId ?: "")
                put("Sensor", sensorId ?: "")
                put("timeInMillis", Date().time)
            }

            val obj = JSONObject().apply {
                put("sensorId", sensorId ?: "")
                put("status", status)
                put("rawData", rawData)
            }
            Log.d("commingg", "Yuppp")
            authenticateSDKService.postDeviceData(
                environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
                data = obj.toString(),
                token = userToken,
                loaderListener = object : LoaderListener {
                    override fun onShowLoader() {}
                    override fun onHideLoader() {}
                }
            )

            resetDebounceTimer()
        }
    }

    private fun resetDebounceTimer() {
        debounceJob?.cancel() // Cancel any existing timer
        debounceJob = apiScope.launch {
            delay(60 * 60 * 1000L) // 60 minutes in ms
            lastDeviceStatus = null // Reset status after timeout
            println("60 min window expired, status reset.")
        }
    }

    @ReactMethod
    fun startCgmTracky(token: String, envType: String, userData: ReadableMap? = null) {
        try {
            updateUserData(userData)
            userToken = token
            env = envType.lowercase()
            val intent = Intent(currentActivity, StartCGMActivity::class.java)
            intent.putExtra("envType", env)
            currentActivity?.startActivity(intent)
        } catch (e: Exception) {
            Log.e("startCgmTracky", "startCgmTracky: ${e.message}", e)
            captureModuleException("startCgmTracky", e)
        }
    }

    fun isCurrentDateInRange(startDateStr: String, endDateStr: String): Boolean {
        val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())

        val startDate = format.parse(startDateStr)
        val endDate = format.parse(endDateStr)
        val currentDate = Date()

        return startDate != null && endDate != null &&
                !currentDate.before(startDate) && !currentDate.after(endDate)
    }

    @ReactMethod
    fun reconnectCgmTracky(token: String, envType: String, userData: ReadableMap? = null) {
        try {
            updateUserData(userData)
            userToken = token
            env = envType.lowercase()
            currentActivity?.startActivity(
                Intent(currentActivity, StartCGMActivity::class.java).putExtra(
                    "IsForReconnect",
                    true
                ).putExtra("envType", env)
            )
        } catch (e: Exception) {
            Log.e("reconnectCgmTracky", "reconnectCgmTracky: ${e.message}", e)
            captureModuleException("reconnectCgmTracky", e)
        }
    }

    @ReactMethod
    fun openHelpSupport() {
        try {
            val intent = Intent(currentActivity, HelpActivity::class.java)
            currentActivity?.startActivity(intent)
        } catch (e: Exception) {
            Log.e("openHelpSupport", "openHelpSupport: ${e.message}", e)
            captureModuleException("openHelpSupport", e)
        }
    }


    @ReactMethod
    fun observeGlucoseData(
        token: String,
        isForClear: Boolean = false,
        envType: String,
        userData: ReadableMap? = null
    ) {
        try {
            updateUserData(userData)
            userToken = token
            env = envType

            // Ensure we're not already observing
            if (isObserving && glucoseObserver != null) {
                Log.d(
                    "observeGlucoseData",
                    "Already observing glucose data, skipping duplicate setup"
                )
                return
            }

            // Don't prevent observation during batch processing - they can coexist
            // Just log it for monitoring
            if (isBatchProcessing.get()) {
                Log.d(
                    "observeGlucoseData",
                    "Batch processing in progress, but starting observer anyway"
                )
            }

            // Remove existing observer if any to prevent memory leaks
            if (glucoseObserver != null) {
                stopObservingGlucoseDataInternal()
            }

            // Create new observer immediately without delay to prevent data loss
            Handler(Looper.getMainLooper()).post {
                // Create new observer with explicit nullable parameter type
                glucoseObserver = Observer<PocGlucose?> { pocGlucose ->
                    if (pocGlucose == null) {
                        Log.d("observeGlucoseData", "Received null glucose data - skipping")
                        return@Observer
                    }

                    // Check for duplicate using glucoseId instead of timestamp
                    val glucoseId = pocGlucose.glucoseId ?: run {
                        Log.w("observeGlucoseData", "Glucose ID is null, skipping")
                        return@Observer
                    }

                    // Add to queue for processing - ensures no data is lost
                    if (pendingDataQueue.offer(pocGlucose)) {
                        Log.d("observeGlucoseData", "Added glucose data to queue: $glucoseId")
                        triggerDataUpload(envType)
                    } else {
                        Log.e("observeGlucoseData", "Failed to add data to queue: $glucoseId")
                    }
                }

                // Add observer on main thread
                glucoseObserver?.let { observer ->
                    try {
                        mModel.latestGlucose.observeForever(observer)
                        isObserving = true
                        Log.d("observeGlucoseData", "Live glucose observer started successfully")
                    } catch (e: Exception) {
                        Log.e("observeGlucoseData", "Error adding observer: ${e.message}", e)
                        captureModuleException("observeGlucoseData.observeForever", e)
                        glucoseObserver = null
                        isObserving = false
                    }
                }
            }

        } catch (e: Exception) {
            Log.e("observeGlucoseData", "observeGlucoseData: ${e.message}", e)
            captureModuleException("observeGlucoseData", e)
            isObserving = false
            glucoseObserver = null
        }
    }

    // New method to handle data upload from queue
    private fun triggerDataUpload(envType: String) {
        // If upload is already in progress, just return
        if (uploadJob?.isActive == true) {
            return
        }

        uploadJob = apiScope.launch {
            while (pendingDataQueue.isNotEmpty()) {
                val pocGlucose = pendingDataQueue.poll() ?: continue

                // Check for duplicate using glucoseId
                val glucoseId = pocGlucose.glucoseId ?: continue

                if (!processedGlucoseIds.add(glucoseId)) {
                    Log.d("triggerDataUpload", "Already processed: $glucoseId, skipping")
                    continue
                }

                val currentTime = System.currentTimeMillis()
                val lastSyncData = prefsHelper?.lastSyncData
                val dataAge = if (lastSyncData != null) {
                    currentTime - lastSyncData.timeInMillis
                } else {
                    0L
                }

                // If data is recent (within 4 minutes), upload immediately
                if (lastSyncData == null || dataAge <= 4 * 60 * 1000L) {
                    handleGlucoseData(pocGlucose, envType)
                    Log.d("triggerDataUpload", "Uploaded recent data: $glucoseId")
                } else {
                    // Data is old, need batch processing
                    // Put it back in queue for batch to handle
                    pendingDataQueue.offer(pocGlucose)
                    Log.d("triggerDataUpload", "Old data detected, triggering batch: $glucoseId")

                    // Trigger batch process on main thread
                    withContext(Dispatchers.Main) {
                        observeAllGlucoseData(userToken, false, envType)
                    }
                    break // Exit loop to let batch take over
                }

                // Small delay to avoid overwhelming the API
                delay(100)
            }
        }
    }

    private fun handleGlucoseData(pocGlucose: PocGlucose, envType: String) {
        try {
            // Additional safety check
            if (pocGlucose.glucoseId == null) {
                Log.w("handleGlucoseData", "Glucose ID is null, skipping processing")
                return
            }

            Log.d("handleGlucoseData", "Processing glucose data: ${pocGlucose.glucoseId}")
            if (pocGlucose.errorCode == enumError.NONE) {
                /* if (pocGlucose.showGlucoseMG > 0) { */
                val dto: GlucoseLog = mapToDto(pocGlucose)
                val logs: ArrayList<GlucoseLog> = ArrayList()
                logs.add(dto)

                val request: GlucoseLogRequest =
                    GlucoseLogRequest(vendor = "GoodFlip", logs = logs)
                val gson: Gson = GsonBuilder().create()

                // Attach sensorId at the top level of the payload sent to /cgm/logs
                val singlePayloadJsonObject = JSONObject(gson.toJson(request)).apply {
                    val activeSensorId =
                        prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
                    put("sensorId", activeSensorId)
                }
                val json = singlePayloadJsonObject.toString()

                Log.d("Glucose data 3 min==> ", "Glucose data 3 min==> final Json: $json")
                // String → captureModuleException puts JSON in scope extra "data" (clipped); INFO avoids error-rate noise.
                // captureModuleException("handleGlucoseData==>postCGMData", json, level = SentryLevel.INFO)
                authenticateSDKService.postCGMData(
                    environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
                    data = json,
                    token = userToken,
                    responseListener = object : AuthenticateSDKService.ResponseListener {
                        override fun onResponseSuccess(response: String) {
                            updateSyncMetadata(pocGlucose)
                            // captureModuleException(
                            //     "handleGlucoseData.postCGMData.success",
                            //     json,
                            //     extras =
                            //         mapOf(
                            //             "pocGlucose" to pocGlucose.toString(),
                            //             "sdk_response" to response
                            //         ),
                            //     level = SentryLevel.INFO
                            // )
                            Log.d("CGM Data", "Single glucose data uploaded successfully")
                        }

                        override fun onResponseFail() {
                            val lastSync = prefsHelper?.lastSyncData
                            val contextData =
                                "failure_reason=Failed to upload single glucose data;" +
                                        "pocGlucose=${pocGlucose};" +
                                        "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
                                        "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
                            captureModuleException(
                                "handleGlucoseData.postCGMData.fail",
                                "$json | $contextData",
                                level = SentryLevel.WARNING
                            )
                            Log.e("CGM Data", "Failed to upload single glucose data")
                        }
                    }
                )
                /*   } */
                /* else {
                     Log.d(
                         "handleGlucoseData",
                         "Glucose value is 0 or negative: ${pocGlucose.showGlucoseMG}"
                     )
                 } */
            } else {
                Log.d("handleGlucoseData", "Glucose data has error: ${pocGlucose.errorCode}")
                val lastSync = prefsHelper?.lastSyncData
                val contextData =
                    "failure_reason=Glucose data has error: ${pocGlucose.errorCode};" +
                            "pocGlucose=${pocGlucose};" +
                            "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
                            "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
                captureModuleException(
                    "handleGlucoseData==>postCGMData",
                    "Glucose data has error: ${pocGlucose.errorCode} | $contextData",
                    level = SentryLevel.INFO
                )
                handleGlucoseError(pocGlucose, envType)
            }
        } catch (e: Exception) {
            Log.e("handleGlucoseData", "Error handling glucose data: ${e.message}", e)
            val lastSync = prefsHelper?.lastSyncData
            val contextData =
                "failure_reason=Exception in handleGlucoseData;" +
                        "pocGlucose=${pocGlucose};" +
                        "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
                        "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
            captureModuleException(
                "handleGlucoseData",
                RuntimeException("handleGlucoseData failed | $contextData", e)
            )
        }
    }

    // Extract error handling logic
    private fun handleGlucoseError(pocGlucose: PocGlucose, envType: String) {
        try {
            // Additional safety check for deviceId
            if (pocGlucose.deviceId == null) {
                Log.e("handleGlucoseError", "Device ID is null, cannot process error")
                return
            }

            val deviceInfo = mModel.getDeviceInfo(pocGlucose.deviceId.toInt())
            deviceInfo?.let {
                val statusId = when (pocGlucose.errorCode) {
                    enumError.ERROR_FLOODING_WATER -> {
                        Log.d("handleGlucoseError", "Moisture detected")
                        DeviceStatus.MOISTURE_DETECT.id
                    }

                    enumError.ERROR_CURRENT_SMALL,
                    enumError.ERROR_NOISE,
                    enumError.ERROR_SENSITIVITY_ATTENUATION -> {
                        Log.d("handleGlucoseError", "Weak signal detected")
                        DeviceStatus.WEAK_SIGNAL.id
                    }

                    else -> {
                        Log.d(
                            "handleGlucoseError",
                            "Common error detected: ${pocGlucose.errorCode}"
                        )
                        DeviceStatus.ERROR_COMMON.id
                    }
                }

                postEventDataToAPI(it, statusId, it.qrMessage, envType)
            } ?: run {
                Log.e(
                    "handleGlucoseError",
                    "Device info not found for deviceId: ${pocGlucose.deviceId}"
                )
            }
        } catch (e: Exception) {
            Log.e("handleGlucoseError", "Error handling glucose error: ${e.message}", e)
            captureModuleException("handleGlucoseError", e)
        }
    }

    @ReactMethod
    fun observeAllGlucoseData(
        token: String,
        isForClear: Boolean = false,
        envType: String,
        userData: ReadableMap? = null
    ) {
        Log.d("function call", "observeAllGlucoseData")

        // Use atomic compareAndSet to prevent concurrent batch processing
        if (!isBatchProcessing.compareAndSet(false, true)) {
            Log.d("observeAllGlucoseData", "Batch processing already in progress, skipping duplicate call")
            return
        }

        userToken = token
        updateUserData(userData)
        env = envType

        CoroutineScope(Dispatchers.IO).launch {
            try {
                // Process any pending data from queue first to ensure nothing is lost
                val pendingData = mutableListOf<PocGlucose>()
                while (pendingDataQueue.isNotEmpty()) {
                    pendingDataQueue.poll()?.let { pendingData.add(it) }
                }

                if (pendingData.isNotEmpty()) {
                    Log.d("observeAllGlucoseData", "Found ${pendingData.size} items in pending queue")
                }

                val lastSyncData = prefsHelper?.lastSyncData

                val lastSyncDataJson = Gson().toJson(lastSyncData).toString()
                Log.d("lastSyncData: ", lastSyncDataJson)

                val currentTime = System.currentTimeMillis()
                val dataAge = if (lastSyncData != null) currentTime - lastSyncData.timeInMillis else 0L

                if (lastSyncData != null && dataAge > 3 * 60 * 1000L) {
            
                    val glucoseData = mModel.getGlucoseBetweenTime(lastSyncData.timeInMillis)

                    val glucoseCount = glucoseData?.size ?: 0
                    Log.d("observeAllGlucoseData", "Retrieved ${glucoseCount} records from DB")

                    // Combine database data with pending queue data
                    val allData = mutableListOf<PocGlucose>()
                    glucoseData?.let { allData.addAll(it) }
                    allData.addAll(pendingData)

                    // Log BEFORE sorting to see database order
                    allData.take(5).forEachIndexed { i, data ->
                        Log.d("BEFORE Sort [$i]", "timeInMillis: ${data.timeInMillis}, glucoseId: ${data.glucoseId}")
                    }

                    // Sort by timestamp to ensure chronological order (oldest first)
                    // This is critical when device reconnects after being out of range
                    val sortedGlucoseData = allData.sortedBy { it.timeInMillis }

                    // Log AFTER sorting to verify chronological order
                    sortedGlucoseData.take(5).forEachIndexed { i, data ->
                        Log.d("AFTER Sort [$i]", "timeInMillis: ${data.timeInMillis}, glucoseId: ${data.glucoseId}")
                    }

                    if (sortedGlucoseData.isNotEmpty()) {
                        Log.d("observeAllGlucoseData", "✅ Sorted ${sortedGlucoseData.size} records chronologically")
                        Log.d("observeAllGlucoseData", "Oldest: ${sortedGlucoseData.first().timeInMillis}")
                        Log.d("observeAllGlucoseData", "Newest: ${sortedGlucoseData.last().timeInMillis}")
                    }

                    if (sortedGlucoseData.isNotEmpty()) {
                        processBatchDataAndStartObserver(
                            sortedGlucoseData,
                            isForClear,
                            envType
                        )
                    } else {
                        Log.d(
                            "observeAllGlucoseData",
                            "No historical data found, starting live observation"
                        )
                        withContext(Dispatchers.Main) {
                            if (!isObserving) {
                                observeGlucoseData(userToken, isForClear, envType)
                            }
                        }
                    }
                } else {
                    Log.d("observeAllGlucoseData", "Processing ${pendingData.size} pending items")
                    // No batch needed, but process pending data if any
                    if (pendingData.isNotEmpty()) {
                        Log.d("observeAllGlucoseData", "Processing ${pendingData.size} pending items")
                        pendingData.forEach { data ->
                            val glucoseId = data.glucoseId
                            if (glucoseId != null && processedGlucoseIds.add(glucoseId)) {
                                handleGlucoseData(data, envType)
                            }
                        }
                    }

                    withContext(Dispatchers.Main) {
                        if (!isObserving) {
                            observeGlucoseData(userToken, isForClear, envType)
                        }
                    }
                }
            } catch (e: Exception) {
                Log.e("observeAllGlucoseData", "Error in batch processing: ${e.message}", e)
                captureModuleException("observeAllGlucoseData", e)
            } finally {
                // Always reset the flag
                isBatchProcessing.set(false)
                Log.d("observeAllGlucoseData", "Batch processing completed, flag reset")
            }
        }
    }

    // New method to handle batch processing and ensure proper sequencing
    private suspend fun processBatchDataAndStartObserver(
        dataList: List<PocGlucose>,
        isForClear: Boolean = false,
        envType: String
    ) {
        try {
            Log.d(
                "processBatchDataAndStartObserver",
                "Starting batch processing with ${dataList.size} records"
            )

            val success = processBatchDataSynchronously(dataList, envType)

            if (success) {
                Log.d("processBatchDataAndStartObserver", "Batch processing completed successfully")
            } else {
                Log.w("processBatchDataAndStartObserver", "Batch processing had failures")
            }

            // Don't reset flag here - it's reset in observeAllGlucoseData's finally block

            // Ensure we're on main thread and not already observing
            withContext(Dispatchers.Main) {
                if (!isObserving) {
                    Log.d(
                        "processBatchDataAndStartObserver",
                        "Starting live observer after batch completion"
                    )
                    observeGlucoseData(userToken, isForClear, envType)
                } else {
                    Log.d(
                        "processBatchDataAndStartObserver",
                        "Already observing, skipping duplicate observer setup"
                    )
                }
            }

        } catch (e: Exception) {
            Log.e("processBatchDataAndStartObserver", "Error in batch processing: ${e.message}", e)
            captureModuleException("processBatchDataAndStartObserver", e)
            // Start live observation even on error
            withContext(Dispatchers.Main) {
                if (!isObserving) {
                    observeGlucoseData(userToken, isForClear, envType)
                }
            }
        }
    }

    @ReactMethod
    fun getTrackLibDbPath(promise: Promise) {
        try {
            val context = reactApplicationContext
            // Replace with the ACTUAL database name used by track-lib
            val dbFile = context.getDatabasePath("database.db")
            Log.d("dbFile======", dbFile.absolutePath)
            promise.resolve(dbFile.absolutePath)
        } catch (e: Exception) {
            Log.d("dbFileerrrr======", "dbFileerrrr======", e)
            captureModuleException("getTrackLibDbPath", e)
            promise.reject("ERROR_DB_PATH", e)
        }
    }

    @ReactMethod
    fun resetCgmState() {
        Log.d("resetCgmState", "Resetting CGM state for logout")

        try {
            // Stop all observations (also removes glucoseObserver + deviceObserver)
            stopObservingGlucoseData()

            debounceJob?.cancel()
            debounceJob = null

            uploadJob?.cancel()
            uploadJob = null

            userToken = ""
            lastDeviceStatus = null

            processedGlucoseIds.clear()
            pendingDataQueue.clear()

            isBatchProcessing.set(false)

            prefsHelper?.clearQRInformation()

        } catch (e: Exception) {
            Log.e("resetCgmState", "Error resetting CGM state: ${e.message}", e)
            captureModuleException("resetCgmState", e)
        }
    }

    /**
     * Get all CGM log file paths
     * Returns array of absolute file paths to CGM log files (CSV format)
     */
    @ReactMethod
    fun getCgmLogFiles(promise: Promise) {
        try {
            val context = reactApplicationContext
            val logFiles = mutableListOf<String>()

            // Get the primary log folder path
            val externalFilesDir = context.getExternalFilesDir(android.os.Environment.DIRECTORY_DOCUMENTS)
            val primaryFolder = externalFilesDir?.absolutePath

            // Get the fallback log folder path
            val fallbackFolder = android.os.Environment.getExternalStorageDirectory().absolutePath +
                    File.separatorChar + "PocData" + File.separatorChar + "logger"

            // Check primary folder
            if (primaryFolder != null) {
                val primaryDir = File(primaryFolder)
                if (primaryDir.exists() && primaryDir.isDirectory) {
                    val files = primaryDir.listFiles { file ->
                        file.isFile && file.name.endsWith(".csv")
                    }
                    files?.forEach { file ->
                        logFiles.add(file.absolutePath)
                    }
                }
            }

            // Check fallback folder
            val fallbackDir = File(fallbackFolder)
            if (fallbackDir.exists() && fallbackDir.isDirectory) {
                val files = fallbackDir.listFiles { file ->
                    file.isFile && file.name.endsWith(".csv")
                }
                files?.forEach { file ->
                    // Only add if not already in list
                    if (!logFiles.contains(file.absolutePath)) {
                        logFiles.add(file.absolutePath)
                    }
                }
            }

            Log.d("getCgmLogFiles", "Found ${logFiles.size} log files")

            // Convert list to WritableArray
            val resultArray = Arguments.createArray()
            logFiles.forEach { path ->
                resultArray.pushString(path)
            }

            promise.resolve(resultArray)

        } catch (e: Exception) {
            Log.e("getCgmLogFiles", "Error getting log files: ${e.message}", e)
            captureModuleException("getCgmLogFiles", e)
            promise.reject("ERROR_GET_LOG_FILES", e.message, e)
        }
    }

    // Updated batch processing method with better sync control
    private suspend fun processBatchDataSynchronously(
        dataList: List<PocGlucose>,
        envType: String
    ): Boolean {
        if (dataList.isEmpty()) {
            Log.d("processBatchDataSynchronously", "No data to upload")
            return true
        }

        // Filter out records already uploaded in a previous session.
        // processedGlucoseIds is in-memory and resets on process restart, so we use the
        // persisted lastSyncData.glucoseId as an additional cross-restart dedup boundary.
        //
        // IMPORTANT: glucoseId resets to 0 when a new sensor is attached, so the filter
        // must ONLY apply when the record belongs to the SAME device session (same deviceId).
        // If the deviceId differs (new sensor), all records pass through unconditionally.
        val lastSyncData = prefsHelper?.lastSyncData
        val lastSyncedGlucoseId = lastSyncData?.glucoseId
        val lastSyncedDeviceId = lastSyncData?.deviceId

        val filteredList = if (lastSyncedGlucoseId != null && lastSyncedDeviceId != null) {
            dataList.filter { item ->
                // Different device/sensor → always include (glucoseIds restarted from 0)
                item.deviceId != lastSyncedDeviceId ||
                        item.glucoseId == null ||
                        item.glucoseId!! > lastSyncedGlucoseId
            }.also {
                val skipped = dataList.size - it.size
                if (skipped > 0) {
                    Log.d("processBatchDataSynchronously", "Skipped $skipped already-synced records (deviceId=$lastSyncedDeviceId, glucoseId <= $lastSyncedGlucoseId)")
                }
            }
        } else {
            dataList
        }

        if (filteredList.isEmpty()) {
            Log.d("processBatchDataSynchronously", "No new data after glucoseId filter, skipping upload")
            return true
        }

        val batchSize = 40
        val chunks = filteredList.chunked(batchSize)
        var lastSyncedRecord: PocGlucose? = null
        var allBatchesSuccessful = true

        Log.d("processBatchDataSynchronously", "Starting batch upload with ${chunks.size} batches")

        for ((index, batch) in chunks.withIndex()) {
            try {
                val transformedLogs = batch.map { pocGlucose ->
                    CgmLog(
                        timeInMillis = pocGlucose.timeInMillis,
                        countdownMinutes = pocGlucose.countdownMinutes,
                        countdownDays = pocGlucose.countdownDays,
                        hypoglycemiaEarlyWarnMinutes = pocGlucose.hypoglycemiaEarlyWarnMinutes,
                        showGlucoseMG = pocGlucose.showGlucoseMG,
                        glucoseId = pocGlucose.glucoseId,
                        name = pocGlucose.name,
                        bytes = pocGlucose.bytes,
                        showGlucose = pocGlucose.showGlucose,
                        Ib = pocGlucose.ib,
                        Iw = pocGlucose.iw,
                        countdownHours = pocGlucose.countdownHours,
                        T = pocGlucose.t,
                        year = pocGlucose.year,
                        month = pocGlucose.month,
                        day = pocGlucose.day,
                        hour = pocGlucose.hour,
                        minute = pocGlucose.minute,
                        trendObject = com.mytatvarnsdk.model.TrendObject(
                            trendId = pocGlucose.trend.trendId,
                            drawableId = pocGlucose.trend.drawableId,
                            widgetImg = pocGlucose.trend.widgetImg,
                            apsChangeRate = pocGlucose.trend.apsChangeRate
                        ),
                        glucoseStatusObject = com.mytatvarnsdk.model.GlucoseStatusObject(
                            statusId = pocGlucose.glucoseStatus.statusId
                        ),
                        errorObject = com.mytatvarnsdk.model.ErrorObject(
                            errorId = pocGlucose.errorCode.errorId,
                            sound = pocGlucose.errorCode.sound
                        )
                    )
                }

                if (transformedLogs.isEmpty()) {
                    Log.d(
                        "processBatchDataSynchronously",
                        "Batch $index skipped - no valid glucose readings"
                    )
                    continue
                }

                // Log the actual timestamps being sent to verify order
                transformedLogs.forEachIndexed { i, log ->
                    Log.d(
                        "Batch $index Record $i",
                        "timeInMillis: ${log.timeInMillis}, glucoseId: ${log.glucoseId}"
                    )
                }

                val allResult = AllCGMLogRequest(vendor = "GoodFlip", logs = transformedLogs)

                // Attach sensorId at the top level of the batch payload sent to /cgm/logs
                val sensorPayloadJsonObject = JSONObject(Gson().toJson(allResult)).apply {
                    val activeSensorId =
                        prefsHelper?.qrInformation?.sensor ?: lastConnectCgm ?: ""
                    put("sensorId", activeSensorId)
                }
                val json = sensorPayloadJsonObject.toString()

                // Check if logs array is empty - skip API call if so
                if (transformedLogs.isEmpty()) {
                    Log.d(
                        "processBatchDataSynchronously",
                        "Batch $index skipped - logs array is empty, not hitting API"
                    )
                    continue
                }

                Log.d(
                    "Batch Upload",
                    "Processing batch $index with ${transformedLogs.size} records"
                )
                Log.d(
                    "Batch Upload",
                    "First timestamp: ${transformedLogs.firstOrNull()?.timeInMillis}, Last timestamp: ${transformedLogs.lastOrNull()?.timeInMillis}"
                )
                logLongJson("Batch $index JSON=>>> ", json)

                val uploadSuccessful = uploadBatchSynchronously(json, index, envType)

                if (uploadSuccessful) {
                    lastSyncedRecord = batch.lastOrNull()
                    // Update sync metadata after each successful batch
                    updateSyncMetadata(lastSyncedRecord)
                    // captureModuleException(
                    //     "processBatchDataSynchronously.batchSuccess",
                    //     json,
                    //     extras =
                    //         mapOf(
                    //             "batchIndex" to index.toString(),
                    //             "lastSyncedRecord" to (lastSyncedRecord?.toString() ?: "")
                    //         ),
                    //     level = SentryLevel.INFO
                    // )

                    Log.d("Batch Upload", "✅ Batch $index uploaded and synced successfully")
                } else {
                    allBatchesSuccessful = false
                    Log.e("Batch Upload", "❌ Batch $index failed")
                    val lastSync = prefsHelper?.lastSyncData
                    val contextData =
                        "failure_reason=postCGMData returned failure;" +
                                "batchIndex=$index;" +
                                "batch_records=${batch.size};" +
                                "payload_chars=${json.length};" +
                                "first_glucoseId=${batch.firstOrNull()?.glucoseId ?: ""};" +
                                "last_glucoseId=${batch.lastOrNull()?.glucoseId ?: ""};" +
                                "first_timeInMillis=${batch.firstOrNull()?.timeInMillis ?: ""};" +
                                "last_timeInMillis=${batch.lastOrNull()?.timeInMillis ?: ""};" +
                                "lastSync_glucoseId=${lastSync?.glucoseId ?: ""};" +
                                "lastSync_timeInMillis=${lastSync?.timeInMillis ?: ""}"
                    captureModuleException(
                        "processBatchDataSynchronously.batchUploadFailed",
                        "postCGMData returned failure for batch $index | $contextData",
                        level = SentryLevel.WARNING
                    )
                    // Continue with next batch instead of breaking (optional based on your needs)
                    // break
                }

                // Rate limiting between batches
                delay(500L)

            } catch (e: Exception) {
                Log.e("Batch Upload", "❌ Batch $index exception: ${e.message}", e)
                captureModuleException(
                    "processBatchDataSynchronously.batch",
                    RuntimeException(
                        "processBatchDataSynchronously.batch failed | batchIndex=$index",
                        e
                    )
                )
                allBatchesSuccessful = false
                // Continue processing other batches
            }
        }

        // Handle error status for the last processed record
        lastSyncedRecord?.let { record ->
            if (record.errorCode != enumError.NONE) {
                handleGlucoseError(record, envType)
            }
        }

        Log.d(
            "processBatchDataSynchronously",
            "Batch processing completed. Overall success: $allBatchesSuccessful"
        )
        return allBatchesSuccessful
    }


    @ReactMethod
    fun stopObservingGlucoseData() {
        stopObservingGlucoseDataInternal()
    }

    // Internal method that doesn't clear the queue (used during restarts)
    private fun stopObservingGlucoseDataInternal() {
        Log.d("stopObservingGlucoseData", "Stopping glucose data observation")

        try {
            debounceJob?.cancel()
            debounceJob = null

            Handler(Looper.getMainLooper()).post {
                try {
                    glucoseObserver?.let { mModel.latestGlucose.removeObserver(it) }
                    isObserving = false
                    Log.d("stopObservingGlucoseData", "Glucose observer removed successfully")

                    // Also remove device observer to prevent accumulation
                    deviceObserver?.let { mModel.device.removeObserver(it) }
                    Log.d("stopObservingGlucoseData", "Device observer removed successfully")
                } catch (e: Exception) {
                    Log.e("stopObservingGlucoseData", "Error removing observers: ${e.message}", e)
                    captureModuleException("stopObservingGlucoseData.removeObservers", e)
                }
            }

            glucoseObserver = null
            deviceObserver = null
            lastDeviceStatus = null

        } catch (e: Exception) {
            Log.e("stopObservingGlucoseData", "Error stopping observer: ${e.message}", e)
            captureModuleException("stopObservingGlucoseData", e)
        }
    }

    // Helper method for synchronous batch upload
    private suspend fun uploadBatchSynchronously(
        json: String,
        batchIndex: Int,
        envType: String
    ): Boolean {
        return suspendCoroutine { continuation ->
            try {
                authenticateSDKService.postCGMData(
                    environment = if (envType.lowercase() == "uat") TATVA_ENVIRONMENT.STAGE else TATVA_ENVIRONMENT.PROD,
                    data = json,
                    token = userToken,
                    responseListener = object : AuthenticateSDKService.ResponseListener {
                        override fun onResponseSuccess(response: String) {
                            continuation.resume(true)
                        }

                        override fun onResponseFail() {
                            continuation.resume(false)
                        }
                    }
                )
            } catch (e: Exception) {
                Log.e("uploadBatchSynchronously", "Exception in batch $batchIndex: ${e.message}", e)
                captureModuleException(
                    "uploadBatchSynchronously",
                    RuntimeException(
                        "uploadBatchSynchronously failed | batchIndex=$batchIndex",
                        e
                    )
                )
                continuation.resume(false)
            }
        }
    }

    private fun updateSyncMetadata(lastRecord: PocGlucose?) {
        lastRecord?.let {
            apiScope.launch {
                metadataLock.withLock {
                    try {
                        val syncData = SyncMeta(
                            System.currentTimeMillis(),
                            it.timeInMillis,
                            it.deviceId,
                            it.glucoseId
                        )
                        prefsHelper?.lastSyncData = syncData

                        // Mark this glucose ID as processed
                        it.glucoseId?.let { id -> processedGlucoseIds.add(id) }

                        Log.d(
                            "Sync Metadata",
                            "Sync metadata updated: glucoseId=${it.glucoseId}, time=${it.timeInMillis}"
                        )
                    } catch (e: Exception) {
                        Log.e("updateSyncMetadata", "Error updating sync metadata: ${e.message}", e)
                        captureModuleException("updateSyncMetadata", e)
                    }
                }
            }
        }
    }


    fun logLongJson(tag: String, message: String) {
        val maxLogSize = 4000
        for (i in 0..message.length / maxLogSize) {
            val start = i * maxLogSize
            val end = (i + 1) * maxLogSize
            if (start < message.length) {
                Log.d(tag, message.substring(start, minOf(end, message.length)))
            }
        }
    }

    fun mapToDto(glucose: PocGlucose): GlucoseLog {
        val dto: GlucoseLog = GlucoseLog()
        dto.timeInMillis = glucose.getTimeInMillis()
        dto.countdownMinutes = glucose.getCountdownMinutes()
        dto.countdownHours = glucose.getCountdownHours()
        dto.countdownDays = glucose.getCountdownDays()
        dto.hypoglycemiaEarlyWarnMinutes = glucose.getHypoglycemiaEarlyWarnMinutes()
        dto.showGlucoseMG = glucose.getShowGlucoseMG()
        dto.glucoseId = glucose.getGlucoseId()
        dto.name = glucose.getName()
        dto.showGlucose = glucose.getShowGlucose()
        dto.Ib = glucose.getIb()
        dto.Iw = glucose.getIw()
        dto.T = glucose.getT()
        dto.year = glucose.getYear()
        dto.month = glucose.getMonth()
        dto.day = glucose.getDay()
        dto.hour = glucose.getHour()
        dto.minute = glucose.getMinute()

        // Convert byte[] to List<Integer>
        dto.bytes = ArrayList()
        for (b in glucose.getBytes()) {
            dto.bytes?.add(b.toInt() and 0xFF) // Prevent negative values
        }

        // Trend
        val trendObj: TrendObject = TrendObject()
        trendObj.trendId = glucose.getTrend().getTrendId()
        trendObj.drawableId = glucose.getTrend().getDrawableId()
        trendObj.widgetImg = glucose.getTrend().getWidgetImg()
        trendObj.apsChangeRate = glucose.getTrend().getApsChangeRate()
        dto.trendObject = trendObj

        // Status
        val statusObj: GlucoseStatusObject = GlucoseStatusObject()
        statusObj.statusId = glucose.getGlucoseStatus().getStatusId()
        dto.glucoseStatusObject = statusObj

        // Error
        val errorObj: ErrorObject = ErrorObject()
        errorObj.errorId = glucose.getErrorCode().getErrorId()
        errorObj.sound = glucose.getErrorCode().getSound().toString()
        dto.errorObject = errorObj

        return dto
    }


    @ReactMethod
    fun sendDataToReact(data: String, status: String, eventName: String) {
        Log.d("sendDataToReact: data ", data)

        try {
            val map: WritableMap = Arguments.createMap().apply {
                putString("data", data)
                putString("status", status)
            }

            Log.d("sendDataToReact: ", map.toString())

            mReactContext?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
                ?.emit(eventName, map)
        } catch (e: Exception) {
            Log.e("Error sendDataToReact: ", e.message.toString(), e)
            captureModuleException(
                "sendDataToReact",
                RuntimeException(
                    "sendDataToReact failed | eventName=$eventName;status=$status",
                    e
                )
            )
        }
    }

}