package club.clubk.ktag.apps.medic

import android.annotation.SuppressLint
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
import android.bluetooth.le.ScanResult
import android.content.Context
import android.util.Log
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.viewModelScope
import androidx.preference.PreferenceManager
import club.clubk.ktag.apps.core.DeviceModel
import club.clubk.ktag.apps.core.HexUtils
import club.clubk.ktag.apps.core.R
import club.clubk.ktag.apps.core.ble.Packet
import club.clubk.ktag.apps.core.ui.ColorUtils
import club.clubk.ktag.apps.core.ui.theme.KTagBlue
import club.clubk.ktag.apps.core.ui.theme.KTagPurple
import club.clubk.ktag.apps.core.ui.theme.KTagRed
import club.clubk.ktag.apps.sharedservices.BleViewModel
import club.clubk.ktag.apps.sharedservices.DeviceInfo
import club.clubk.ktag.apps.sharedservices.DevicePreferenceKeys
import club.clubk.ktag.apps.sharedservices.MedicPreferenceKeys
import club.clubk.ktag.apps.sharedservices.SharedMqttClient
import club.clubk.ktag.apps.sharedservices.getIntPref
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MedicViewModel : BleViewModel() {

    override val logTag = "KTag Medic"

    companion object {
        private const val CLEANUP_INTERVAL_MS = 500L
        private const val RSSI_HYSTERESIS = 20
        private const val HEAL_DURATION_MS = 3000L
        private const val HEAL_RETRY_INTERVAL_MS = 5000L
        private const val HEAL_COOLDOWN_MS = 4000L
    }

    // Device list
    private val _devices = mutableStateListOf<DeviceModel>()
    val devices: List<DeviceModel> get() = _devices

    // KOTH possession state
    var kothPossessionText by mutableStateOf("The game has not started.")
        private set

    var kothPossessionColor by mutableStateOf(Color.White)
        private set

    // Settings
    var autoHealSetting by mutableStateOf(AutoHealSetting.NONE)
        private set

    var minimumRssi by mutableStateOf(-60)
        private set

    private var deviceTtlMs = DeviceModel.DEFAULT_TTL_MS

    val backgroundColorForAutoHeal: Color
        get() = when (autoHealSetting) {
            AutoHealSetting.RED -> ColorUtils.makePastel(KTagRed)
            AutoHealSetting.BLUE -> ColorUtils.makePastel(KTagBlue)
            AutoHealSetting.ALL -> ColorUtils.makePastel(KTagPurple)
            AutoHealSetting.NONE -> Color.White
        }

    // Cleanup job
    private var cleanupJob: Job? = null

    // Heal retry job
    private var healRetryJob: Job? = null

    // Track last heal time per device to avoid spamming
    private val lastHealedAt = mutableMapOf<String, Long>()

    private val possessionListener = object : club.clubk.ktag.apps.sharedservices.MqttMessageListener {
        override fun onMessageReceived(topic: String, payload: ByteArray) {
            val message = String(payload)
            onPossessionUpdated(message)
        }
    }

    override fun onStatusPacket(result: ScanResult) {
        val status = Packet.Status(result)
        addOrRefreshDevice(
            bleAddress = result.device.address,
            rssi = result.rssi,
            color = Color(status.primary_color),
            health = status.health
        )
    }

    override fun updateDeviceInfoFromRepository(infoMap: Map<String, DeviceInfo>) {
        applyDeviceInfoUpdate(_devices, infoMap)
    }

    fun initialize(context: Context) {
        appContext = context.applicationContext
        initBluetooth(context)
        loadPreferences(context)
        initMqtt()
        initDeviceInfo(context)
    }

    private fun initMqtt() {
        try {
            val battlefield = SharedMqttClient.battlefield
            SharedMqttClient.publishHello("KTag Medic App", "Medic")
            SharedMqttClient.subscribe("KTag/$battlefield/KotH/Listen", listenListener)
            SharedMqttClient.subscribe("KTag/$battlefield/KotH/Possession", possessionListener)
        } catch (e: Exception) {
            Log.e(logTag, "Failed to initialize MQTT: ${e.message}")
        }
    }

    fun loadPreferences(context: Context) {
        val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
        minimumRssi = sharedPrefs.getIntPref(MedicPreferenceKeys.MIN_RSSI, MedicPreferenceKeys.DEFAULT_MIN_RSSI)
        autoHealSetting = AutoHealSetting.fromInt(
            sharedPrefs.getIntPref(MedicPreferenceKeys.AUTO_HEAL, MedicPreferenceKeys.DEFAULT_AUTO_HEAL)
        )
        deviceTtlMs = sharedPrefs.getIntPref(DevicePreferenceKeys.DEVICE_TTL, DevicePreferenceKeys.DEFAULT_DEVICE_TTL) * 1000
        Log.d(logTag, "minimumRSSI: $minimumRssi, autoHealSetting: $autoHealSetting")
    }

    @SuppressLint("MissingPermission")
    fun startScanning() {
        startBleScanning()

        cleanupJob?.cancel()
        cleanupJob = viewModelScope.launch {
            while (true) {
                delay(CLEANUP_INTERVAL_MS)
                cleanupExpiredDevices()
            }
        }

        healRetryJob?.cancel()
        healRetryJob = viewModelScope.launch {
            while (true) {
                delay(HEAL_RETRY_INTERVAL_MS)
                retryHealingIfNeeded()
            }
        }
    }

    @SuppressLint("MissingPermission")
    fun stopScanning() {
        stopBleScanning()
        cleanupJob?.cancel()
        cleanupJob = null
        healRetryJob?.cancel()
        healRetryJob = null
        stopAdvertising()
    }

    private fun cleanupExpiredDevices() {
        val iterator = _devices.listIterator()
        while (iterator.hasNext()) {
            val device = iterator.next()
            val updated = device.withDecrementedTtl(CLEANUP_INTERVAL_MS.toInt())
            if (updated.isExpired) {
                iterator.remove()
                lastHealedAt.remove(device.bleAddress)
            } else {
                iterator.set(updated)
            }
        }
    }

    private fun addOrRefreshDevice(
        bleAddress: String,
        rssi: Int,
        color: Color,
        health: Int
    ) {
        val shouldAutoHeal = when (autoHealSetting) {
            AutoHealSetting.ALL -> true
            AutoHealSetting.RED -> color == Color.Red
            AutoHealSetting.BLUE -> color == Color.Blue
            AutoHealSetting.NONE -> false
        }

        val existingIndex = _devices.indexOfFirst { it.bleAddress == bleAddress }

        if (existingIndex == -1) {
            // Device not in list
            Log.d(logTag, "New device: $bleAddress, health=$health, rssi=$rssi, minRssi=$minimumRssi")
            if (health <= 0 && rssi >= minimumRssi) {
                // Tagged-out device, close enough to show
                if (shouldAutoHeal) {
                    healDevice(bleAddress)
                }
                val deviceName = deviceInfoRepository?.getName(bleAddress) ?: "KTag Device"
                val info = deviceInfoRepository?.getInfo(bleAddress)
                Log.d(logTag, "Adding device: $bleAddress with name: $deviceName")
                _devices.add(DeviceModel(
                    name = deviceName,
                    version = if (info != null) String.format("SystemK v%d.%02d", info.majorVersion, info.minorVersion) else "",
                    deviceType = info?.deviceTypeName ?: "",
                    id = 1,
                    image = R.drawable.ktag_shield,
                    bleAddress = bleAddress,
                    rssi = rssi,
                    color = color
                ))
            }
        } else {
            // Device already in list
            if (health <= 0 && rssi >= (minimumRssi - RSSI_HYSTERESIS)) {
                // Still tagged out and close enough; refresh TTL.
                _devices[existingIndex] = _devices[existingIndex].withResetTtl(deviceTtlMs)
            } else {
                // Device is alive or too far; remove from list.
                Log.d(logTag, "Removing device: $bleAddress")
                _devices.removeAt(existingIndex)
            }
        }
    }

    @SuppressLint("MissingPermission")
    fun healDevice(bleAddress: String) {
        Log.i(logTag, "Healing $bleAddress")

        lastHealedAt[bleAddress] = System.currentTimeMillis()

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

        val tag = Packet.Tag(HexUtils.hexStringToByteArray(bleAddress, ':'))
        val adData = AdvertiseData.Builder()
            .addManufacturerData(0xFFFF, tag.GetBytes())
            .build()

        stopAdvertising()

        btAdvertiser?.startAdvertising(settings, adData, advertisingCallback)

        advertisingJob = viewModelScope.launch {
            delay(HEAL_DURATION_MS)
            stopAdvertising()
        }
    }

    private fun retryHealingIfNeeded() {
        if (autoHealSetting == AutoHealSetting.NONE) return

        val now = System.currentTimeMillis()
        for (device in _devices.toList()) {
            val shouldAutoHeal = when (autoHealSetting) {
                AutoHealSetting.ALL -> true
                AutoHealSetting.RED -> device.color == Color.Red
                AutoHealSetting.BLUE -> device.color == Color.Blue
                AutoHealSetting.NONE -> false
            }

            if (shouldAutoHeal) {
                val lastHealed = lastHealedAt[device.bleAddress] ?: 0L
                if (now - lastHealed >= HEAL_COOLDOWN_MS) {
                    Log.d(logTag, "Retrying heal for ${device.bleAddress}")
                    healDevice(device.bleAddress)
                    return // Only heal one device per cycle to avoid BLE conflicts
                }
            }
        }
    }

    fun onDeviceClicked(device: DeviceModel) {
        healDevice(device.bleAddress)
        _devices.remove(device)
    }

    private fun onPossessionUpdated(message: String) {
        kothPossessionText = message
        kothPossessionColor = when {
            message.contains("Red") -> KTagRed
            message.contains("Blue") -> KTagBlue
            else -> Color.White
        }
    }

    fun cleanup() {
        stopScanning()
        stopAdvertising()
        try {
            val battlefield = SharedMqttClient.battlefield
            SharedMqttClient.unsubscribe("KTag/$battlefield/KotH/Listen", listenListener)
            SharedMqttClient.unsubscribe("KTag/$battlefield/KotH/Possession", possessionListener)
        } catch (e: Exception) {
            Log.e(logTag, "Error unsubscribing: ${e.message}")
        }
        cleanupDeviceInfo()
    }

    override fun onCleared() {
        super.onCleared()
        cleanup()
    }
}
