package club.clubk.ktag.apps.sharedservices

import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.json.JSONObject

/**
 * Device info extracted from Hello packets.
 */
data class DeviceInfo(
    val name: String = "",
    val userDefinedName: String = "",
    val deviceType: Int = 0,
    val deviceTypeName: String = "",
    val majorVersion: Int = 0,
    val minorVersion: Int = 0
) {
    /** Returns userDefinedName if set, otherwise name. */
    val displayName: String
        get() = userDefinedName.ifEmpty { name }
}

/**
 * Singleton repository for managing device info stored in SharedPreferences.
 * Provides reactive updates via StateFlow for Compose UI integration.
 */
class DeviceInfoRepository private constructor(context: Context) {

    private val prefs: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
    private val _deviceInfo = MutableStateFlow<Map<String, DeviceInfo>>(emptyMap())

    /** Reactive stream of device info mappings (MAC address -> DeviceInfo) */
    val deviceInfo: StateFlow<Map<String, DeviceInfo>> = _deviceInfo.asStateFlow()

    private val prefsListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
        if (key == DeviceInfoPreferenceKeys.DEVICE_INFO_JSON) {
            _deviceInfo.value = loadInfoFromPrefs()
        }
    }

    init {
        _deviceInfo.value = loadInfoFromPrefs()
        prefs.registerOnSharedPreferenceChangeListener(prefsListener)
    }

    private fun loadInfoFromPrefs(): Map<String, DeviceInfo> {
        val json = prefs.getString(DeviceInfoPreferenceKeys.DEVICE_INFO_JSON, null)
            ?: return emptyMap()

        return try {
            val jsonObject = JSONObject(json)
            val map = mutableMapOf<String, DeviceInfo>()
            jsonObject.keys().forEach { key ->
                val infoObj = jsonObject.getJSONObject(key)
                map[key] = DeviceInfo(
                    name = infoObj.optString("name", ""),
                    userDefinedName = infoObj.optString("userDefinedName", ""),
                    deviceType = infoObj.optInt("deviceType", 0),
                    deviceTypeName = infoObj.optString("deviceTypeName", ""),
                    majorVersion = infoObj.optInt("majorVersion", 0),
                    minorVersion = infoObj.optInt("minorVersion", 0)
                )
            }
            map
        } catch (e: Exception) {
            emptyMap()
        }
    }

    private fun saveInfoToPrefs(info: Map<String, DeviceInfo>) {
        val jsonObject = JSONObject()
        info.forEach { (mac, deviceInfo) ->
            val infoObj = JSONObject().apply {
                put("name", deviceInfo.name)
                put("userDefinedName", deviceInfo.userDefinedName)
                put("deviceType", deviceInfo.deviceType)
                put("deviceTypeName", deviceInfo.deviceTypeName)
                put("majorVersion", deviceInfo.majorVersion)
                put("minorVersion", deviceInfo.minorVersion)
            }
            jsonObject.put(mac, infoObj)
        }
        prefs.edit()
            .putString(DeviceInfoPreferenceKeys.DEVICE_INFO_JSON, jsonObject.toString())
            .apply()
    }

    /**
     * Get the info for a device by MAC address.
     */
    fun getInfo(macAddress: String): DeviceInfo? {
        return _deviceInfo.value[macAddress]
    }

    /**
     * Get the name for a device by MAC address.
     */
    fun getName(macAddress: String): String {
        val info = _deviceInfo.value[macAddress] ?: return "KTag Device $macAddress"
        val display = info.displayName
        return display.ifEmpty { "KTag Device $macAddress" }
    }

    /**
     * Set or update the user-defined name for a device.
     */
    fun setName(macAddress: String, name: String) {
        val existing = _deviceInfo.value[macAddress] ?: DeviceInfo()
        if (existing.userDefinedName == name) return
        val updated = _deviceInfo.value.toMutableMap()
        updated[macAddress] = existing.copy(userDefinedName = name)
        saveInfoToPrefs(updated)
        _deviceInfo.value = updated
    }

    /**
     * Set or update the info for a device.
     * Returns true if the info actually changed.
     */
    fun setInfo(macAddress: String, info: DeviceInfo): Boolean {
        val existing = _deviceInfo.value[macAddress]
        val merged = if (existing != null) {
            info.copy(userDefinedName = existing.userDefinedName)
        } else {
            info
        }
        if (existing == merged) return false
        val updated = _deviceInfo.value.toMutableMap()
        updated[macAddress] = merged
        saveInfoToPrefs(updated)
        _deviceInfo.value = updated
        return true
    }

    /**
     * Merge incoming info with existing info.
     * Incoming info takes precedence.
     */
    fun mergeInfo(incoming: Map<String, DeviceInfo>) {
        val merged = _deviceInfo.value.toMutableMap()
        merged.putAll(incoming)
        saveInfoToPrefs(merged)
        _deviceInfo.value = merged
    }

    /**
     * Get all device info as a JSON string for MQTT publishing.
     */
    fun getAllInfoAsJson(): String {
        val jsonObject = JSONObject()
        _deviceInfo.value.forEach { (mac, info) ->
            val infoObj = JSONObject().apply {
                put("name", info.name)
                put("userDefinedName", info.userDefinedName)
                put("deviceType", info.deviceType)
                put("deviceTypeName", info.deviceTypeName)
                put("majorVersion", info.majorVersion)
                put("minorVersion", info.minorVersion)
            }
            jsonObject.put(mac, infoObj)
        }
        return jsonObject.toString()
    }

    companion object {
        @Volatile
        private var instance: DeviceInfoRepository? = null

        fun getInstance(context: Context): DeviceInfoRepository {
            return instance ?: synchronized(this) {
                instance ?: DeviceInfoRepository(context.applicationContext).also {
                    instance = it
                }
            }
        }
    }
}
