package club.clubk.ktag.apps.mine

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.MinePreferenceKeys
import club.clubk.ktag.apps.sharedservices.getIntPref
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
 * State machine for the Mine's arming cycle.
 */
sealed class MineState {
    /** Mine is armed and ready to tag devices */
    data object Armed : MineState()

    /** Mine is currently showing explosion animation */
    data object Tagging : MineState()

    /** Mine is rearming with countdown */
    data class Rearming(val countdownSeconds: Int) : MineState()
}

class MineViewModel : BleViewModel() {

    override val logTag = "KTag Mine"

    companion object {
        private const val CLEANUP_INTERVAL_MS = 500L
        private const val RSSI_HYSTERESIS = 20
        private const val TAG_DURATION_MS = 3000L
        private const val DEFAULT_DAMAGE = 100
        private var tagEventNumber: Byte = 0
    }

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

    // Mine state machine
    var mineState by mutableStateOf<MineState>(MineState.Armed)
        private set

    var rearmTimeSeconds by mutableStateOf(5)
        private set

    private var rearmJob: Job? = null

    fun onExplosionAnimationComplete() {
        transitionToRearming()
    }

    private fun transitionToRearming() {
        rearmJob?.cancel()
        mineState = MineState.Rearming(rearmTimeSeconds)

        rearmJob = viewModelScope.launch {
            var countdown = rearmTimeSeconds
            while (countdown > 0) {
                delay(1000L)
                countdown--
                mineState = MineState.Rearming(countdown)
            }
            resetTaggedDevices()
            mineState = MineState.Armed
        }
    }

    private fun resetTaggedDevices() {
        val iterator = _devices.listIterator()
        while (iterator.hasNext()) {
            val device = iterator.next()
            if (device.hasBeenTagged) {
                iterator.set(device.withReset())
            }
        }
    }

    // Settings
    var targetTeamSetting by mutableStateOf(TargetTeamSetting.ALL)
        private set

    var minimumRssi by mutableStateOf(-60)
        private set

    var damage by mutableStateOf(DEFAULT_DAMAGE)
        private set

    private var deviceTtlMs = DeviceModel.DEFAULT_TTL_MS

    val backgroundColorForTargetTeam: Color
        get() = when (targetTeamSetting) {
            TargetTeamSetting.RED -> ColorUtils.makePastel(KTagRed)
            TargetTeamSetting.BLUE -> ColorUtils.makePastel(KTagBlue)
            TargetTeamSetting.ALL -> ColorUtils.makePastel(KTagPurple)
        }

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

    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,
            teamId = status.team_ID
        )
    }

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

    fun initialize(context: Context) {
        initBluetooth(context)
        loadPreferences(context)
        initDeviceInfo(context)
    }

    fun loadPreferences(context: Context) {
        val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
        minimumRssi = sharedPrefs.getIntPref(MinePreferenceKeys.MIN_RSSI, MinePreferenceKeys.DEFAULT_MIN_RSSI)
        targetTeamSetting = TargetTeamSetting.fromInt(
            sharedPrefs.getIntPref(MinePreferenceKeys.TARGET_TEAM, MinePreferenceKeys.DEFAULT_TARGET_TEAM)
        )
        damage = sharedPrefs.getIntPref(MinePreferenceKeys.DAMAGE, MinePreferenceKeys.DEFAULT_DAMAGE)
        rearmTimeSeconds = sharedPrefs.getIntPref(MinePreferenceKeys.REARM_TIME, MinePreferenceKeys.DEFAULT_REARM_TIME)
        deviceTtlMs = sharedPrefs.getIntPref(DevicePreferenceKeys.DEVICE_TTL, DevicePreferenceKeys.DEFAULT_DEVICE_TTL) * 1000
        Log.d(logTag, "minimumRSSI: $minimumRssi, targetTeamSetting: $targetTeamSetting, damage: $damage, rearmTime: $rearmTimeSeconds")
    }

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

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

    @SuppressLint("MissingPermission")
    fun stopScanning() {
        stopBleScanning()
        cleanupJob?.cancel()
        cleanupJob = 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()
            } else {
                iterator.set(updated)
            }
        }
    }

    private fun addOrRefreshDevice(
        bleAddress: String,
        rssi: Int,
        color: Color,
        health: Int,
        teamId: Byte
    ) {
        Log.d(logTag, "RSSI $rssi (min $minimumRssi), health: $health, teamId: $teamId")

        val matchesTeam = when (targetTeamSetting) {
            TargetTeamSetting.ALL -> true
            TargetTeamSetting.RED -> teamId == 1.toByte()
            TargetTeamSetting.BLUE -> teamId == 2.toByte()
        }

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

        if (!matchesTeam) {
            if (existingIndex != -1) {
                Log.d(logTag, "Removing device (wrong team): $bleAddress")
                _devices.removeAt(existingIndex)
            }
            return
        }

        if (health <= 0) {
            if (existingIndex != -1) {
                val existingDevice = _devices[existingIndex]
                if (!existingDevice.isTaggedOut) {
                    Log.d(logTag, "Device tagged out, showing status: $bleAddress")
                    _devices[existingIndex] = existingDevice.withTaggedOut(deviceTtlMs)
                }
            }
            return
        }

        val isInRange = rssi >= minimumRssi

        if (existingIndex == -1) {
            val deviceName = deviceInfoRepository?.getName(bleAddress) ?: "KTag Device"
            val info = deviceInfoRepository?.getInfo(bleAddress)
            val newDevice = 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_gray,
                bleAddress = bleAddress,
                rssi = rssi,
                color = color,
                health = health,
                inRange = rssi >= minimumRssi
            )
            _devices.add(newDevice)
            Log.d(logTag, "Adding device: $bleAddress, inRange: $isInRange")

            if (isInRange) {
                tagDevice(bleAddress)
                _devices[_devices.size - 1] = _devices.last().withTagged()
            }
        } else {
            val existingDevice = _devices[existingIndex]

            if (existingDevice.isTaggedOut) {
                return
            }

            val wasInRange = existingDevice.inRange
            val updatedDevice = existingDevice
                .withResetTtl(deviceTtlMs)
                .withRssi(rssi, minimumRssi)

            _devices[existingIndex] = updatedDevice

            if (isInRange && !wasInRange && !existingDevice.hasBeenTagged) {
                tagDevice(bleAddress)
                _devices[existingIndex] = _devices[existingIndex].withTagged()
                Log.d(logTag, "Device came into range, tagging: $bleAddress")
            }
        }
    }

    @SuppressLint("MissingPermission")
    fun tagDevice(bleAddress: String) {
        if (mineState != MineState.Armed) {
            Log.d(logTag, "Mine not ready - state: $mineState")
            return
        }

        Log.i(logTag, "Tagging $bleAddress with damage $damage")

        mineState = MineState.Tagging

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

        val tagBytes = createTagPacketWithDamage(
            HexUtils.hexStringToByteArray(bleAddress, ':'),
            damage
        )
        val adData = AdvertiseData.Builder()
            .addManufacturerData(0xFFFF, tagBytes)
            .build()

        stopAdvertising()

        btAdvertiser?.startAdvertising(settings, adData, advertisingCallback)

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

    /**
     * Creates a Tag packet with configurable damage.
     * Positive damage deals damage, negative damage heals.
     */
    private fun createTagPacketWithDamage(targetBdAddr: ByteArray, damageAmount: Int): ByteArray {
        val reversedAddr = ByteArray(6)
        for (i in 0 until 6) {
            reversedAddr[i] = targetBdAddr[5 - i]
        }

        val damageLsb = (damageAmount and 0xFF).toByte()
        val damageMsb = ((damageAmount shr 8) and 0xFF).toByte()

        return byteArrayOf(
            'K'.code.toByte(),
            'T'.code.toByte(),
            'a'.code.toByte(),
            'g'.code.toByte(),
            0x03,                   // PACKET_TYPE_TAG
            tagEventNumber++,       // Event number
            4,                      // Tx Power Level (dBm)
            0x03,                   // Protocol
            0x00,                   // Team ID
            0xFF.toByte(),          // Player ID
            damageLsb,              // Damage (lsb)
            damageMsb,              // Damage (msb)
            0xFF.toByte(),          // Primary Color RED
            0xFF.toByte(),          // Primary Color GREEN
            0xFF.toByte(),          // Primary Color BLUE
            0xFE.toByte(),          // Primary Color BRIGHTNESS
            reversedAddr[0],
            reversedAddr[1],
            reversedAddr[2],
            reversedAddr[3],
            reversedAddr[4],
            reversedAddr[5],
            0xFF.toByte(),
            0xFF.toByte(),
            0xFF.toByte(),
            0xFF.toByte(),
            0xFF.toByte()
        )
    }

    fun onDeviceClicked(device: DeviceModel) {
        tagDevice(device.bleAddress)
    }

    fun cleanup() {
        stopScanning()
        stopAdvertising()
        rearmJob?.cancel()
        rearmJob = null
        cleanupDeviceInfo()
    }

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