package club.clubk.ktag.apps.sharedservices

import android.annotation.SuppressLint
import android.bluetooth.BluetoothManager
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.BluetoothLeAdvertiser
import android.bluetooth.le.BluetoothLeScanner
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import club.clubk.ktag.apps.core.DeviceModel
import club.clubk.ktag.apps.core.ble.Packet
import kotlinx.coroutines.launch

/**
 * Base ViewModel for subapps that scan for KTag BLE devices.
 *
 * Provides shared BLE scanning/advertising infrastructure and DeviceInfo integration.
 * Subclasses must implement [onStatusPacket] to handle Status packet content and
 * [updateDeviceInfoFromRepository] to apply name/version updates to their device list.
 */
abstract class BleViewModel : ViewModel() {

    protected abstract val logTag: String

    // BLE
    protected var btScanner: BluetoothLeScanner? = null
    protected var btAdvertiser: BluetoothLeAdvertiser? = null
    protected var isAdvertising = false
    protected var advertisingJob: kotlinx.coroutines.Job? = null

    // Device Info
    protected var deviceInfoRepository: DeviceInfoRepository? = null
    protected var deviceInfoMqttSync: DeviceInfoMqttSync? = null

    // Context for UI interactions (e.g. Toast)
    protected var appContext: Context? = null

    /** Listener that shows incoming MQTT Listen messages as a Toast. */
    val listenListener = object : MqttMessageListener {
        override fun onMessageReceived(topic: String, payload: ByteArray) {
            val text = String(payload)
            Handler(Looper.getMainLooper()).post {
                appContext?.let { ctx ->
                    Toast.makeText(ctx, "MQTT: $text", Toast.LENGTH_LONG).show()
                }
            }
        }
    }

    @SuppressLint("MissingPermission")
    protected val advertisingCallback = object : AdvertiseCallback() {
        override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
            Log.d(logTag, "Advertising onStartSuccess")
            isAdvertising = true
        }

        override fun onStartFailure(errorCode: Int) {
            Log.e(logTag, "Advertising onStartFailure: $errorCode")
            isAdvertising = false
        }
    }

    @SuppressLint("MissingPermission")
    protected val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            if (Packet.IsKTagStatusPacket(result)) {
                onStatusPacket(result)
            } else if (Packet.IsKTagHelloPacket(result)) {
                val hello = Packet.Hello(result)
                val changed = deviceInfoRepository?.setInfo(
                    result.device.address,
                    DeviceInfo(
                        name = hello.device_name,
                        deviceType = hello.device_type,
                        deviceTypeName = Packet.getDeviceTypeName(hello.device_type),
                        majorVersion = hello.systemK_major_version,
                        minorVersion = hello.systemK_minor_version
                    )
                ) ?: false
                if (changed) {
                    deviceInfoMqttSync?.publishCurrentInfo()
                }
            }
        }

        override fun onBatchScanResults(results: List<ScanResult>) {
            results.forEach { onScanResult(0, it) }
        }

        override fun onScanFailed(errorCode: Int) {
            Log.e(logTag, "BLE Scan failed with error code: $errorCode")
        }
    }

    /** Called when a Status packet is received; subclass handles the payload. */
    protected abstract fun onStatusPacket(result: ScanResult)

    @SuppressLint("MissingPermission")
    protected fun initBluetooth(context: Context) {
        val btManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        val btAdapter = btManager.adapter
        btScanner = btAdapter?.bluetoothLeScanner
        btAdvertiser = btAdapter?.bluetoothLeAdvertiser
    }

    protected fun initDeviceInfo(context: Context) {
        try {
            deviceInfoRepository = DeviceInfoRepository.getInstance(context)
            deviceInfoMqttSync = DeviceInfoMqttSync(deviceInfoRepository!!)
            deviceInfoMqttSync?.connect()

            viewModelScope.launch {
                deviceInfoRepository?.deviceInfo?.collect { infoMap ->
                    updateDeviceInfoFromRepository(infoMap)
                }
            }
        } catch (e: Exception) {
            Log.e(logTag, "Failed to initialize device info: ${e.message}")
        }
    }

    /**
     * Called when the DeviceInfo repository updates. Subclass should call
     * [applyDeviceInfoUpdate] with its own device list.
     */
    protected abstract fun updateDeviceInfoFromRepository(infoMap: Map<String, DeviceInfo>)

    /**
     * Applies name/type/version updates from [infoMap] to [devices] in place.
     */
    protected fun applyDeviceInfoUpdate(
        devices: MutableList<DeviceModel>,
        infoMap: Map<String, DeviceInfo>
    ) {
        val iterator = devices.listIterator()
        while (iterator.hasNext()) {
            val device = iterator.next()
            val info = infoMap[device.bleAddress] ?: continue
            val newName = info.displayName.ifEmpty { null } ?: device.name
            val newDeviceType = info.deviceTypeName
            val newVersion = String.format("SystemK v%d.%02d", info.majorVersion, info.minorVersion)
            if (device.name != newName || device.deviceType != newDeviceType || device.version != newVersion) {
                iterator.set(device.copy(name = newName, deviceType = newDeviceType, version = newVersion))
            }
        }
    }

    fun renameDevice(macAddress: String, newName: String) {
        deviceInfoRepository?.setName(macAddress, newName)
        deviceInfoMqttSync?.publishCurrentInfo()
    }

    @SuppressLint("MissingPermission")
    protected fun startBleScanning() {
        try {
            btScanner?.startScan(scanCallback)
            Log.i(logTag, "Started scanning for KTag devices.")
        } catch (e: Exception) {
            Log.e(logTag, "Error starting scan: ", e)
        }
    }

    @SuppressLint("MissingPermission")
    protected fun stopBleScanning() {
        try {
            btScanner?.stopScan(scanCallback)
            Log.i(logTag, "Stopped scanning for KTag devices.")
        } catch (e: Exception) {
            Log.e(logTag, "Error stopping scan: ", e)
        }
    }

    @SuppressLint("MissingPermission")
    protected fun stopAdvertising() {
        try {
            btAdvertiser?.stopAdvertising(advertisingCallback)
        } catch (e: Exception) {
            Log.e(logTag, "Error stopping advertising: ", e)
        }
        isAdvertising = false
        advertisingJob?.cancel()
        advertisingJob = null
    }

    protected fun cleanupDeviceInfo() {
        deviceInfoMqttSync?.cleanup()
    }
}
