package club.clubk.ktag.bletool

import android.annotation.SuppressLint
import android.bluetooth.BluetoothManager
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.Context
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment

private const val TAG = "BLE Scanner"

class ScannerActivity : ComponentActivity() {
    private var isScanning by mutableStateOf(false)
    private val scannedDevices = mutableStateMapOf<String, KTagPacket>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    ScannerScreen(
                        isScanning = isScanning,
                        devices = scannedDevices,
                        onStartScan = { startScanning() },
                        onStopScan = { stopScanning() }
                    )
                }
            }
        }
    }

    @SuppressLint("MissingPermission")
    private fun startScanning() {
        // Clear existing devices when starting a new scan.
        scannedDevices.clear()

        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        val scanner = bluetoothManager.adapter.bluetoothLeScanner ?: run {
            Log.e(TAG, "BLE scanning not supported")
            return
        }

        // Filter for KTag manufacturer data
        val filter = ScanFilter.Builder()
            .setManufacturerData(
                0xFFFF,  // Manufacturer ID
                byteArrayOf(0x4B, 0x54, 0x61, 0x67)  // "KTag"
            )
            .build()

        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()

        scanner.startScan(listOf(filter), settings, scanCallback)
        isScanning = true
    }

    private fun stopScanning() {
        val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
        val scanner = bluetoothManager.adapter.bluetoothLeScanner
        scanner?.stopScan(scanCallback)
        isScanning = false
    }

    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            result.scanRecord?.manufacturerSpecificData?.get(0xFFFF)?.let { data ->
                if (data.size >= 4 &&
                    data[0] == 0x4B.toByte() && // K
                    data[1] == 0x54.toByte() && // T
                    data[2] == 0x61.toByte() && // a
                    data[3] == 0x67.toByte()    // g
                ) {
                    val packet = parseKTagPacket(data)
                    scannedDevices[result.device.address] = packet
                }
            }
        }

        override fun onScanFailed(errorCode: Int) {
            val errorMessage = when (errorCode) {
                ScanCallback.SCAN_FAILED_ALREADY_STARTED -> "Already started"
                ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED -> "App registration failed"
                ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED -> "Feature unsupported"
                ScanCallback.SCAN_FAILED_INTERNAL_ERROR -> "Internal error"
                else -> "Unknown error $errorCode"
            }
            Log.e(TAG, "BLE Scan failed: $errorMessage")
            isScanning = false
        }
    }
}

@Composable
fun ScannerScreen(
    isScanning: Boolean,
    devices: Map<String, KTagPacket>,
    onStartScan: () -> Unit,
    onStopScan: () -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        TitleBox()  // Reuse the same title component

        Button(
            onClick = if (isScanning) onStopScan else onStartScan,
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 8.dp)
        ) {
            Text(if (isScanning) "Stop Scanning" else "Start Scanning")
        }

        LazyColumn(
            modifier = Modifier.weight(1f),
            verticalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            items(devices.entries.toList()) { (address, packet) ->
                PacketCard(address = address, packet = packet)
            }
        }
    }
}

@Composable
fun PacketCard(address: String, packet: KTagPacket) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(8.dp)
        ) {
            // Common header for all packets
            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text(
                    text = address,
                    style = MaterialTheme.typography.bodyMedium,
                    color = MaterialTheme.colorScheme.primary
                )
                Text(
                    text = packet.typeName,
                    style = MaterialTheme.typography.bodyMedium
                )
            }

            Spacer(modifier = Modifier.height(4.dp))

            // Packet-specific content
            when (packet) {
                is InstigateGamePacket -> {
                    PacketRow("Game Length", "${packet.gameLength}ms")
                    PacketRow("Countdown", "${packet.countdownTime}ms")
                }

                is EventPacket -> {
                    PacketRow("Target", packet.targetAddress)
                    PacketRow("Event ID", packet.eventId.toString())
                    PacketRow("Event Data", packet.eventData.toString())
                }

                is TagPacket -> {
                    PacketRow("Team/Player", "${packet.teamId}/${packet.playerId}")
                    PacketRow("Damage", packet.damage.toString())
                    PacketRow("Protocol", packet.protocol.toString())
                    PacketRow("TX Power", "${packet.txPower}dBm")
                    PacketRow("Target", packet.targetAddress)
                    PacketRow("Color", String.format("#%08X", packet.color))
                }

                is ConsolePacket -> {
                    PacketRow("Message", packet.consoleString)
                }

                is StatusPacket -> {
                    PacketRow("Team/Player", "${packet.teamId}/${packet.playerId}")
                    PacketRow("Health", "${packet.health}/${packet.maxHealth}")
                    PacketRow("Protocol", packet.protocol.toString())
                    PacketRow("TX Power", "${packet.txPower}dBm")
                    PacketRow("Primary Color", String.format("#%08X", packet.primaryColor))
                    PacketRow("Secondary Color", String.format("#%08X", packet.secondaryColor))
                    PacketRow("State", packet.systemKState.toString())
                }

                is ParametersPacket -> {
                    PacketRow("Target", packet.targetAddress)
                    PacketRow("Subtype", packet.subtype.toString())
                    PacketRow("Key 1", packet.key1.toString())
                    PacketRow("Value 1", packet.value1.toString())
                    PacketRow("Key 2", packet.key2.toString())
                    PacketRow("Value 2", packet.value2.toString())
                }

                is HelloPacket -> {
                    PacketRow("Version", "${packet.majorVersion}.${packet.minorVersion}")
                    PacketRow("Device Type", packet.deviceType.toString())
                    PacketRow("Team ID", packet.teamId.toString())
                    PacketRow("Device Name", packet.deviceName)
                }
            }

            // Show event number for all packets
            Text(
                text = "Event #${packet.eventNumber}",
                style = MaterialTheme.typography.bodySmall,
                color = MaterialTheme.colorScheme.onSurfaceVariant,
                modifier = Modifier.align(Alignment.End)
            )
        }
    }
}

@Composable
private fun PacketRow(
    label: String,
    value: String,
    modifier: Modifier = Modifier
) {
    Row(
        modifier = modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text(
            text = label,
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.onSurfaceVariant
        )
        Text(
            text = value,
            style = MaterialTheme.typography.bodySmall
        )
    }
}

// Data classes for packet parsing
sealed class KTagPacket {
    abstract val type: Int
    abstract val typeName: String
    abstract val eventNumber: Int
}

data class InstigateGamePacket(
    override val type: Int,
    override val typeName: String = "Instigate Game",
    override val eventNumber: Int,
    val gameLength: Int,
    val countdownTime: Int
) : KTagPacket()

data class EventPacket(
    override val type: Int,
    override val typeName: String = "Event",
    override val eventNumber: Int,
    val targetAddress: String,
    val eventId: Int,
    val eventData: Int
) : KTagPacket()

data class TagPacket(
    override val type: Int,
    override val typeName: String = "Tag",
    override val eventNumber: Int,
    val txPower: Int,
    val protocol: Int,
    val teamId: Int,
    val playerId: Int,
    val damage: Int,
    val color: Int,
    val targetAddress: String
) : KTagPacket()

data class ConsolePacket(
    override val type: Int,
    override val typeName: String = "Console",
    override val eventNumber: Int,
    val consoleString: String
) : KTagPacket()

data class StatusPacket(
    override val type: Int,
    override val typeName: String = "Status",
    override val eventNumber: Int,
    val txPower: Int,
    val protocol: Int,
    val teamId: Int,
    val playerId: Int,
    val health: Int,
    val maxHealth: Int,
    val primaryColor: Int,
    val secondaryColor: Int,
    val systemKState: Int
) : KTagPacket()

data class ParametersPacket(
    override val type: Int,
    override val typeName: String = "Parameters",
    override val eventNumber: Int,
    val targetAddress: String,
    val subtype: Int,
    val key1: Int,
    val value1: Int,
    val key2: Int,
    val value2: Int
) : KTagPacket()

data class HelloPacket(
    override val type: Int,
    override val typeName: String = "Hello",
    override val eventNumber: Int,
    val majorVersion: Int,
    val minorVersion: Int,
    val deviceType: Int,
    val teamId: Int,
    val deviceName: String
) : KTagPacket()

fun parseKTagPacket(data: ByteArray): KTagPacket {
    val type = data[4].toInt()
    val eventNumber = data[5].toInt()

    return when (type) {
        0x01 -> InstigateGamePacket(
            type = type,
            eventNumber = eventNumber,
            gameLength = bytesToInt(data, 6, 4),
            countdownTime = bytesToInt(data, 10, 4)
        )

        0x02 -> EventPacket(
            type = type,
            eventNumber = eventNumber,
            targetAddress = bytesToMacAddress(data, 6),
            eventId = bytesToInt(data, 12, 4),
            eventData = bytesToInt(data, 16, 4)
        )

        0x03 -> TagPacket(
            type = type,
            eventNumber = eventNumber,
            txPower = data[6].toInt(),
            protocol = data[7].toInt(),
            teamId = data[8].toInt(),
            playerId = data[9].toInt(),
            damage = bytesToInt(data, 10, 2),
            color = bytesToInt(data, 12, 4),
            targetAddress = bytesToMacAddress(data, 16)
        )

        0x04 -> ConsolePacket(
            type = type,
            eventNumber = eventNumber,
            consoleString = data.slice(6..26).toByteArray().let { bytes ->
                // Find the position of the first null byte if any
                val nullPos = bytes.indexOfFirst { it == 0.toByte() }
                // If there's a null byte, take only up to that position, otherwise take all bytes
                if (nullPos >= 0) {
                    bytes.slice(0 until nullPos).toByteArray()
                } else {
                    bytes
                }.toString(Charsets.US_ASCII).trim()
            }
        )

        0x05 -> StatusPacket(
            type = type,
            eventNumber = eventNumber,
            txPower = data[6].toInt(),
            protocol = data[7].toInt(),
            teamId = data[8].toInt(),
            playerId = data[8].toInt(),
            health = bytesToInt(data, 10, 2),
            maxHealth = bytesToInt(data, 12, 2),
            primaryColor = bytesToInt(data, 14, 4),
            secondaryColor = bytesToInt(data, 18, 4),
            systemKState = data[22].toInt()
        )

        0x06 -> ParametersPacket(
            type = type,
            eventNumber = eventNumber,
            targetAddress = bytesToMacAddress(data, 6),
            subtype = data[12].toInt(),
            key1 = bytesToInt(data, 13, 2),
            value1 = bytesToInt(data, 15, 4),
            key2 = bytesToInt(data, 19, 2),
            value2 = bytesToInt(data, 21, 4)
        )

        0x07 -> HelloPacket(
            type = type,
            eventNumber = eventNumber,
            majorVersion = data[6].toInt(),
            minorVersion = data[7].toInt(),
            deviceType = bytesToInt(data, 8, 2),
            teamId = data[10].toInt(),
            deviceName = data.slice(11..26).toByteArray().let { bytes ->
                // Find the position of the first null byte if any
                val nullPos = bytes.indexOfFirst { it == 0.toByte() }
                // If there's a null byte, take only up to that position, otherwise take all bytes
                if (nullPos >= 0) {
                    bytes.slice(0 until nullPos).toByteArray()
                } else {
                    bytes
                }.toString(Charsets.US_ASCII).trim()
            }
        )

        else -> StatusPacket(
            type = type,
            typeName = "Unknown",
            eventNumber = eventNumber,
            txPower = 0,
            protocol = 0,
            teamId = 0,
            playerId = 0,
            health = 0,
            maxHealth = 0,
            primaryColor = 0,
            secondaryColor = 0,
            systemKState = 0
        )
    }
}

// Helper function to convert bytes to integers
private fun bytesToInt(data: ByteArray, offset: Int, length: Int): Int {
    var result = 0
    for (i in 0 until length) {
        result = result or ((data[offset + i].toInt() and 0xFF) shl (8 * i))
    }
    return result
}

// Helper function to convert bytes to MAC address string
private fun bytesToMacAddress(data: ByteArray, offset: Int): String {
    return (0..5).joinToString(":") {
        String.format("%02X", data[offset + it])
    }
}