package club.clubk.ktag.apps.koth

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.media.SoundPool
import android.os.SystemClock
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.ble.Packet
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.GamePreferenceKeys
import club.clubk.ktag.apps.sharedservices.SharedMqttClient
import club.clubk.ktag.apps.sharedservices.getIntPref
import club.clubk.ktag.apps.sharedservices.getLongPref
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class KothViewModel : BleViewModel() {

    override val logTag = "KTag KotH"

    companion object {
        private const val TICK_INTERVAL_MS = 100L
        private const val COUNTDOWN_DURATION_MS = 6000L // 5 seconds + 1 second buffer
        const val KING_OF_THE_HILL = "King   of\nthe   Hill"
    }

    // Game state
    var gameState by mutableStateOf<KothState>(KothState.Idle)
        private set

    var gameData by mutableStateOf(GameData.empty())
        private set

    var possession by mutableStateOf<Possession>(Possession.None)
        private set

    var timeRemainingMs by mutableStateOf(0L)
        private set

    var statusText by mutableStateOf(KING_OF_THE_HILL)
        private set

    var showTimer by mutableStateOf(true)
        private set

    var showKingOfTheHillImage by mutableStateOf(true)
        private set

    var endGameResult by mutableStateOf<EndGameResult?>(null)
        private set

    // Team counts for display
    var redCount by mutableStateOf(0)
        private set
    var blueCount by mutableStateOf(0)
        private set

    // Device contacts
    private val _contacts = mutableStateListOf<DeviceModel>()
    val contacts: List<DeviceModel> get() = _contacts

    // Timer
    private var timerJob: Job? = null
    private var timerEndTimeMs = 0L
    private var lastTickRealtime = 0L
    private var previousTimeRemainingSeconds = 0L

    // Sound
    private var soundPool: SoundPool? = null
    private var blipSoundId: Int = 0

    // KotH-specific advertise settings
    private var btAdSettings: AdvertiseSettings? = null

    // Preferences
    private var countdownDelayMs = 30000L
    private var gameDurationMs = 600000L // 10 minutes
    private var minimumRssi = -60
    private var deviceTtlMs = DeviceModel.DEFAULT_TTL_MS

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

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

    fun initialize(context: Context) {
        appContext = context.applicationContext
        initSound(context)
        initBluetooth(context)

        // KotH-specific advertise settings
        btAdSettings = AdvertiseSettings.Builder()
            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
            .setConnectable(false)
            .build()

        loadPreferences(context)
        initMqtt()
        initDeviceInfo(context)

        timeRemainingMs = countdownDelayMs
    }

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

    private fun initSound(context: Context) {
        soundPool = SoundPool.Builder().setMaxStreams(1).build()
        blipSoundId = soundPool?.load(context, R.raw.blip, 1) ?: 0
    }

    private fun loadPreferences(context: Context) {
        val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
        countdownDelayMs = sharedPrefs.getLongPref(GamePreferenceKeys.TIME_UNTIL_COUNTDOWN, GamePreferenceKeys.DEFAULT_TIME_UNTIL_COUNTDOWN) * 1000
        gameDurationMs = sharedPrefs.getLongPref(GamePreferenceKeys.GAME_DURATION, GamePreferenceKeys.DEFAULT_GAME_DURATION) * 60 * 1000
        minimumRssi = sharedPrefs.getIntPref("koth_min_rssi", "-60")
        deviceTtlMs = sharedPrefs.getIntPref(DevicePreferenceKeys.DEVICE_TTL, DevicePreferenceKeys.DEFAULT_DEVICE_TTL) * 1000
    }

    fun processEvent(event: KothEvent) {
        val nextState = getNextState(gameState, event)
        if (nextState != null) {
            Log.d(logTag, "Transitioning from $gameState to $nextState")
            onExitState(gameState)
            gameState = nextState
            onEnterState(nextState)
        } else {
            Log.w(logTag, "No valid transition for $gameState with event $event")
        }
    }

    private fun getNextState(current: KothState, event: KothEvent): KothState? {
        return when (current) {
            is KothState.Idle -> when (event) {
                is KothEvent.Start -> KothState.Initiating
                else -> null
            }
            is KothState.Initiating -> when (event) {
                is KothEvent.Timeout -> KothState.CountingDown
                else -> null
            }
            is KothState.CountingDown -> when (event) {
                is KothEvent.Timeout -> KothState.Playing
                else -> null
            }
            is KothState.Playing -> when (event) {
                is KothEvent.Timeout -> KothState.Finished
                is KothEvent.Stop -> KothState.Finished
                else -> null
            }
            is KothState.Finished -> when (event) {
                is KothEvent.Reset -> KothState.Idle
                else -> null
            }
        }
    }

    @SuppressLint("MissingPermission")
    private fun onEnterState(state: KothState) {
        when (state) {
            is KothState.Idle -> {
                gameData = GameData.empty()
                possession = Possession.None
                timeRemainingMs = countdownDelayMs
                statusText = KING_OF_THE_HILL
                showTimer = true
                showKingOfTheHillImage = true
                endGameResult = null
            }
            is KothState.Initiating -> {
                timeRemainingMs = countdownDelayMs
                startTimer()
                startAdvertisingInstigatingGame()
            }
            is KothState.CountingDown -> {
                timeRemainingMs = COUNTDOWN_DURATION_MS
                startTimer()
            }
            is KothState.Playing -> {
                timeRemainingMs = gameDurationMs
                startTimer()
                stopAdvertising()
            }
            is KothState.Finished -> {
                onGameStop()
            }
        }
    }

    @SuppressLint("MissingPermission")
    private fun onExitState(state: KothState) {
        when (state) {
            is KothState.Initiating -> {
                stopAdvertising()
            }
            is KothState.Playing -> {
                stopTimer()
            }
            else -> {}
        }
    }

    private fun startTimer() {
        timerJob?.cancel()
        val now = SystemClock.elapsedRealtime()
        timerEndTimeMs = now + timeRemainingMs
        lastTickRealtime = now
        timerJob = viewModelScope.launch {
            while (timeRemainingMs > 0) {
                val nextTickTarget = lastTickRealtime + TICK_INTERVAL_MS
                val sleepTime = nextTickTarget - SystemClock.elapsedRealtime()
                if (sleepTime > 0) {
                    delay(sleepTime)
                }
                onTick()
            }
        }
    }

    private fun stopTimer() {
        timerJob?.cancel()
        timerJob = null
    }

    @SuppressLint("MissingPermission")
    private fun onTick() {
        val now = SystemClock.elapsedRealtime()
        val actualElapsedMs = now - lastTickRealtime
        lastTickRealtime = now

        timeRemainingMs = (timerEndTimeMs - now).coerceAtLeast(0)
        val currentSeconds = timeRemainingMs / 1000

        if (currentSeconds != previousTimeRemainingSeconds) {
            playSound()
            previousTimeRemainingSeconds = currentSeconds
        }

        when (gameState) {
            is KothState.Initiating -> {
                updateAdvertisingInstigatingGame()
            }
            is KothState.CountingDown -> {
                val seconds = (timeRemainingMs / 1000).toInt()
                statusText = "$seconds $seconds $seconds $seconds $seconds"
            }
            is KothState.Playing -> {
                cleanupExpiredContacts(actualElapsedMs.toInt())

                val counts = DeviceModel.getTeamCounts(_contacts.toList())
                redCount = counts.red
                blueCount = counts.blue

                possession = when {
                    counts.red > counts.blue -> Possession.Red
                    counts.blue > counts.red -> Possession.Blue
                    else -> Possession.None
                }

                val battlefield = SharedMqttClient.battlefield
                val possessionTopic = "KTag/$battlefield/KotH/Possession"
                gameData = when (possession) {
                    is Possession.Red -> {
                        SharedMqttClient.publish(possessionTopic, "Red has possession.".toByteArray(), qos = 2, retained = false)
                        gameData.addRed(actualElapsedMs)
                    }
                    is Possession.Blue -> {
                        SharedMqttClient.publish(possessionTopic, "Blue has possession.".toByteArray(), qos = 2, retained = false)
                        gameData.addBlue(actualElapsedMs)
                    }
                    is Possession.None -> {
                        SharedMqttClient.publish(possessionTopic, "No one has possession.".toByteArray(), qos = 2, retained = false)
                        gameData
                    }
                }

                statusText = "Red: $redCount\n\nBlue: $blueCount"
            }
            else -> {}
        }

        if (timeRemainingMs <= 0) {
            processEvent(KothEvent.Timeout)
        }
    }

    private fun cleanupExpiredContacts(elapsedMs: Int) {
        val iterator = _contacts.listIterator()
        while (iterator.hasNext()) {
            val contact = iterator.next()
            val updated = contact.withDecrementedTtl(elapsedMs)
            if (updated.isExpired) {
                iterator.remove()
            } else {
                iterator.set(updated)
            }
        }
    }

    private fun onGameStop() {
        showTimer = false
        showKingOfTheHillImage = false

        endGameResult = when {
            gameData.isBlueAhead -> EndGameResult.BlueWins(
                redTime = gameData.redTeamMillis,
                blueTime = gameData.blueTeamMillis
            )
            gameData.isRedAhead -> EndGameResult.RedWins(
                redTime = gameData.redTeamMillis,
                blueTime = gameData.blueTeamMillis
            )
            else -> EndGameResult.Tie(
                redTime = gameData.redTeamMillis,
                blueTime = gameData.blueTeamMillis
            )
        }
    }

    private fun playSound() {
        soundPool?.play(blipSoundId, 1f, 1f, 1, 0, 1f)
    }

    @SuppressLint("MissingPermission")
    private fun startAdvertisingInstigatingGame() {
        val instigatingGame = Packet.InstigatingGame(gameDurationMs.toInt(), timeRemainingMs.toInt())
        val adData = AdvertiseData.Builder()
            .addManufacturerData(0xFFFF, instigatingGame.GetBytes())
            .build()
        btAdvertiser?.startAdvertising(btAdSettings, adData, advertisingCallback)
    }

    @SuppressLint("MissingPermission")
    private fun updateAdvertisingInstigatingGame() {
        stopAdvertising()
        val instigatingGame = Packet.InstigatingGame(gameDurationMs.toInt(), timeRemainingMs.toInt())
        val adData = AdvertiseData.Builder()
            .addManufacturerData(0xFFFF, instigatingGame.GetBytes())
            .build()
        btAdvertiser?.startAdvertising(btAdSettings, adData, advertisingCallback)
    }

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

    @SuppressLint("MissingPermission")
    fun stopScanning() {
        stopBleScanning()
    }

    private fun addOrRefreshDevice(
        bleAddress: String,
        rssi: Int,
        color: Int,
        health: Int,
        teamId: Byte
    ) {
        val existingIndex = _contacts.indexOfFirst { it.bleAddress == bleAddress }

        if (existingIndex == -1 && health > 0) {
            val deviceName = deviceInfoRepository?.getName(bleAddress) ?: "KTag Device"
            val info = deviceInfoRepository?.getInfo(bleAddress)
            _contacts.add(0, DeviceModel(
                name = deviceName,
                version = if (info != null) String.format("SystemK v%d.%02d", info.majorVersion, info.minorVersion) else "",
                deviceType = info?.deviceTypeName ?: "",
                id = 1,
                image = club.clubk.ktag.apps.core.R.drawable.ktag_shield,
                bleAddress = bleAddress,
                rssi = rssi,
                color = Color(color),
                teamId = teamId
            ))
        } else if (existingIndex >= 0) {
            if (health > 0) {
                _contacts[existingIndex] = _contacts[existingIndex].withResetTtl(deviceTtlMs)
            }
            // If health <= 0, let TTL expire naturally
        }
    }

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

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

    fun formatTimeDisplay(millis: Long): String {
        val totalSeconds = millis / 1000
        val minutes = totalSeconds / 60
        val seconds = totalSeconds % 60
        return String.format("%02d:%02d", minutes, seconds)
    }
}

/**
 * Sealed class for end game results.
 */
sealed class EndGameResult {
    abstract val redTime: Long
    abstract val blueTime: Long

    data class RedWins(override val redTime: Long, override val blueTime: Long) : EndGameResult()
    data class BlueWins(override val redTime: Long, override val blueTime: Long) : EndGameResult()
    data class Tie(override val redTime: Long, override val blueTime: Long) : EndGameResult()
}
