package club.clubk.ktag.apps.konfigurator

import android.annotation.SuppressLint
import android.content.Context
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanSettings
import android.util.Log
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import club.clubk.ktag.apps.core.ble.Packet
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

private const val TAG_BLE_SCAN = "BLE Scanner"
private const val TAG_BLE_AD = "BLE Advertiser"

private const val SCAN_HOLDOFF_PERIOD_MS: Long = 5000 // 5 seconds

class BleManager private constructor(context: Context) {
    // Use applicationContext to prevent memory leaks
    private val appContext: Context = context.applicationContext

    // Bluetooth-related properties
    private val bluetoothManager: BluetoothManager =
        appContext.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
    private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
    private val bluetoothLeAdvertiser = bluetoothAdapter?.bluetoothLeAdvertiser
    private val bluetoothLeScanner = bluetoothAdapter?.bluetoothLeScanner
    private var currentAdvertiseCallback: AdvertiseCallback? = null
    private var currentScanCallback: ScanCallback? = null
    private var isAdvertising: Boolean = false
    private var isScanning: Boolean = false

    // Timestamp for the last scan stop
    @Volatile
    private var lastScanStopTimeMs: Long = 0

    // Coroutine scope for managing delays
    private val bleManagerScope = CoroutineScope(Dispatchers.Main + Job()) // Use Main dispatcher for UI-related callbacks if needed, or Default for background work
    private var scanStartJob: Job? = null


    // Singleton instance
    companion object {
        @Volatile
        private var instance: BleManager? = null

        /**
         * Gets or creates the BleManager instance
         *
         * @param context The context used to get the application context
         * @return BleManager instance
         */
        fun getInstance(context: Context): BleManager {
            // Double-checked locking pattern
            return instance ?: synchronized(this) {
                instance ?: BleManager(context).also { instance = it }
            }
        }
    }

    // Utility function to check if Bluetooth is available and enabled
    private fun isBluetoothAvailable(): Boolean {
        return bluetoothAdapter != null && bluetoothAdapter.isEnabled
    }

    @SuppressLint("MissingPermission")
    fun startAdvertising(data: ByteArray, advertiseCallback: AdvertiseCallback) {
        stopAdvertising()
        if (!isBluetoothAvailable()) {
            Log.e(TAG_BLE_AD, "Bluetooth is not enabled!")
            throw IllegalStateException("Bluetooth is not enabled!")
        }

        currentAdvertiseCallback = advertiseCallback

        val settings = AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
            .setConnectable(false)
            .setTimeout(0)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
            .build()

        // Calculate available space for manufacturer data
        // 2 bytes for manufacturer ID
        // Maximum total payload is 31 bytes
        // Format: [Length (1 byte)] [Type (1 byte)] [Manufacturer ID (2 bytes)] [Data (remaining bytes)]
        val maxManufacturerDataSize = 27  // 31 - 4 bytes overhead

        val truncatedData = if (data.size > maxManufacturerDataSize) {
            Log.w(
                TAG_BLE_AD,
                "Data exceeded maximum size (${data.size} > $maxManufacturerDataSize bytes), truncating..."
            )
            data.copyOfRange(0, maxManufacturerDataSize)
        } else {
            data
        }

        Log.d(TAG_BLE_AD, "Advertisement structure:")
        Log.d(TAG_BLE_AD, "- Total payload max: 31 bytes")
        Log.d(TAG_BLE_AD, "- Overhead: 4 bytes (Length: 1, Type: 1, Manufacturer ID: 2)")
        Log.d(TAG_BLE_AD, "- Available for manufacturer data: $maxManufacturerDataSize bytes")
        Log.d(TAG_BLE_AD, "- Actual data size: ${truncatedData.size} bytes")
        Log.d(TAG_BLE_AD, "- Data: ${truncatedData.joinToString(" ") { String.format("%02X", it) }}")

        val advertiseData = AdvertiseData.Builder()
            .addManufacturerData(0xFFFF, truncatedData)
            .build()

        Log.i(
            TAG_BLE_AD, "Starting advertisement with settings: Mode=${settings.mode}, " +
                    "TxPower=${settings.txPowerLevel}, Connectable=${settings.isConnectable}"
        )

        try {
            bluetoothLeAdvertiser?.startAdvertising(settings, advertiseData, advertiseCallback)
            isAdvertising = true
        } catch (e: SecurityException) {
            Log.e(TAG_BLE_AD, "Permission missing for starting advertisement", e)
            advertiseCallback.onStartFailure(AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR) // Notify caller
        }
    }

    @SuppressLint("MissingPermission")
    fun stopAdvertising() {
        Log.d(TAG_BLE_AD, "Attempting to stop advertising...")
        currentAdvertiseCallback?.let { callback ->
            try {
                bluetoothLeAdvertiser?.stopAdvertising(callback)
                Log.i(TAG_BLE_AD, "Advertisement stopping process initiated.")
            } catch (e: SecurityException) {
                Log.e(TAG_BLE_AD, "Permission missing for stopping advertisement", e)
            } finally {
                currentAdvertiseCallback = null // Clear callback regardless of success/failure to stop
                isAdvertising = false
            }
        } ?: Log.d(TAG_BLE_AD, "No active advertisement to stop or advertiser is null.")
        // isAdvertising should be reliably set to false when stop is called or if no active ad
        if (currentAdvertiseCallback == null) isAdvertising = false
    }


    @SuppressLint("MissingPermission")
    fun startScanning(scanCallback: ScanCallback) {
        // Cancel any pending scan start operation
        scanStartJob?.cancel()

        scanStartJob = bleManagerScope.launch {
            val currentTimeMs = System.currentTimeMillis()
            val timeSinceLastScanStopMs = currentTimeMs - lastScanStopTimeMs

            if (isScanning) { // If already scanning with a *different* callback, stop it first
                Log.d(TAG_BLE_SCAN, "Already scanning, but a new scan request received. Stopping current scan first.")
                internalStopScan() // Use an internal stop that doesn't update lastScanStopTimeMs yet
            }

            if (lastScanStopTimeMs > 0 && timeSinceLastScanStopMs < SCAN_HOLDOFF_PERIOD_MS) {
                val delayNeededMs = SCAN_HOLDOFF_PERIOD_MS - timeSinceLastScanStopMs
                Log.i(TAG_BLE_SCAN, "Scan holdoff active. Delaying scan start by ${delayNeededMs}ms")
                delay(delayNeededMs)
            }

            // After potential delay, stop any existing scan (e.g. if one was running with a different callback)
            // This also ensures that if stopScanning() was called very recently, we respect that.
            if (currentScanCallback != null && currentScanCallback != scanCallback) {
                internalStopScan() // Stop previous scan if a new one is requested with different callback
            }


            currentScanCallback = scanCallback // Set the new callback

            val filter = ScanFilter.Builder()
                .setManufacturerData(
                    0xFFFF, // Manufacturer ID
                    byteArrayOf(0x4B, 0x54, 0x61, 0x67) // "KTag"
                )
                .build()
            val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()

            if (!isBluetoothAvailable()) {
                Log.e(TAG_BLE_SCAN, "Bluetooth is not enabled! Cannot start scan.")
                scanCallback.onScanFailed(ScanCallback.SCAN_FAILED_INTERNAL_ERROR) // Notify caller
                return@launch
            }

            Log.i(TAG_BLE_SCAN, "Starting scan...")
            try {
                bluetoothLeScanner?.startScan(listOf(filter), settings, scanCallback)
                isScanning = true
            } catch (e: SecurityException) {
                Log.e(TAG_BLE_SCAN, "Permission missing for starting scan", e)
                scanCallback.onScanFailed(ScanCallback.SCAN_FAILED_INTERNAL_ERROR) // Notify caller
                isScanning = false // Ensure state is correct
            }
        }
    }

    @SuppressLint("MissingPermission")
    fun stopScanning() {
        // Cancel any pending scan start job, as we are now explicitly stopping.
        scanStartJob?.cancel()
        internalStopScan()
    }

    // Internal stop scan function to avoid recursive calls and manage state correctly
    @SuppressLint("MissingPermission")
    private fun internalStopScan() {
        Log.d(TAG_BLE_SCAN, "Attempting to stop scanning (internal)...")
        currentScanCallback?.let { callback ->
            try {
                bluetoothLeScanner?.stopScan(callback)
                Log.i(TAG_BLE_SCAN, "Scan stopping process initiated.")
            } catch (e: SecurityException) {
                Log.e(TAG_BLE_SCAN, "Permission missing for stopping scan", e)
            } finally {
                // This is a good place to record the stop time
                lastScanStopTimeMs = System.currentTimeMillis()
                currentScanCallback = null // Clear callback
                isScanning = false         // Update scanning state
            }
        } ?: Log.d(TAG_BLE_SCAN, "No active scan to stop or scanner is null.")
        // Ensure scanning state is false if no callback
        if (currentScanCallback == null) isScanning = false
    }


    @SuppressLint("MissingPermission")
    suspend fun configureDevice(configPacket: Packet.Parameters, targetAddress: String): Boolean {
        // Ensure configureDevice uses the managed startScanning/stopScanning
        return withTimeout(5000) { // Timeout for the whole configuration operation
            val advertisementDetected = CompletableDeferred<Boolean>()
            var tempAdvertiseCallback: AdvertiseCallback? = null
            var tempScanCallback: ScanCallback? = null

            try {
                tempAdvertiseCallback = object : AdvertiseCallback() {
                    override fun onStartSuccess(settingsInEffect: AdvertiseSettings?) {
                        super.onStartSuccess(settingsInEffect)
                        Log.d(TAG_BLE_AD,"Config advertisement started successfully.")
                        // Only start scanning AFTER advertisement has successfully started
                        tempScanCallback = object : ScanCallback() {
                            override fun onScanResult(callbackType: Int, result: ScanResult) {
                                Log.d(TAG_BLE_SCAN, ">>> ScanCallback.onScanResult ENTERED")
                                if (Packet.IsKTagParametersPacket(result)) {
                                    val scannedParams = Packet.Parameters(result)
                                    Log.d(TAG_BLE_SCAN, "Parameter packet scanned, checking for parameter match...")
                                    if (result.device.address == targetAddress
                                        && scannedParams.subtype == Packet.PARAMETER_SUBTYPE_ACKNOWLEDGE_CHANGE
                                        && scannedParams.key1 == configPacket.key1
                                        && scannedParams.value1 == configPacket.value1
                                        && scannedParams.key2 == configPacket.key2
                                        && scannedParams.value2 == configPacket.value2
                                    ) {
                                        Log.i(TAG_BLE_SCAN, "Parameters match, configuration successful for ${result.device.address}!")
                                        if (!advertisementDetected.isCompleted) {
                                            advertisementDetected.complete(true)
                                        }
                                    }
                                }
                            }

                            override fun onScanFailed(errorCode: Int) {
                                Log.e(TAG_BLE_SCAN, "Config scan failed with error: $errorCode")
                                if (!advertisementDetected.isCompleted) {
                                    advertisementDetected.complete(false)
                                }
                            }
                        }
                        // Stop any in-progress regular scan, then start the ACK scan
                        // directly — bypassing startScanning's holdoff, which is the same
                        // duration as the configure timeout and would always cause a miss.
                        scanStartJob?.cancel()
                        internalStopScan()
                        val ackFilter = ScanFilter.Builder()
                            .setManufacturerData(0xFFFF, byteArrayOf(0x4B, 0x54, 0x61, 0x67))
                            .build()
                        val ackSettings = ScanSettings.Builder()
                            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
                        try {
                            bluetoothLeScanner?.startScan(listOf(ackFilter), ackSettings, tempScanCallback!!)
                        } catch (e: SecurityException) {
                            Log.e(TAG_BLE_SCAN, "Permission missing for ACK scan", e)
                            if (!advertisementDetected.isCompleted) advertisementDetected.complete(false)
                        }
                    }

                    override fun onStartFailure(errorCode: Int) {
                        super.onStartFailure(errorCode)
                        Log.e(TAG_BLE_AD, "Failed to start config advertisement, error: $errorCode")
                        if (!advertisementDetected.isCompleted) {
                            advertisementDetected.complete(false)
                        }
                    }
                }

                Log.d(TAG_BLE_AD, "Sending configuration packet to $targetAddress...")
                this@BleManager.startAdvertising(configPacket.GetBytes(),
                    tempAdvertiseCallback
                )

                val success = advertisementDetected.await() // Wait for scan result or ad failure
                success

            } catch (e: TimeoutCancellationException) {
                Log.e(TAG_BLE_SCAN, "Config confirmation timed out for $targetAddress")
                false
            } finally {
                Log.d(TAG_BLE_SCAN, "Cleaning up configureDevice resources for $targetAddress")
                // Stop the specific advertising and scanning session for this configuration attempt
                // Check callbacks before stopping to ensure they were set
                tempAdvertiseCallback?.let { this@BleManager.stopAdvertising() }
                // Stop the ACK scan directly so we don't stamp a new holdoff timestamp
                // between consecutive configure packets.
                tempScanCallback?.let {
                    try {
                        bluetoothLeScanner?.stopScan(it)
                    } catch (e: SecurityException) {
                        Log.e(TAG_BLE_SCAN, "Permission missing for stopping ACK scan", e)
                    }
                }
            }
        }
    }
}
