package com.saltylab.uwbtag

import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.*
import android.bluetooth.le.*
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.card.MaterialCardView
import com.google.android.material.switchmaterial.SwitchMaterial
import com.google.android.material.textfield.TextInputEditText
import com.google.gson.Gson
import com.saltylab.uwbtag.databinding.ActivityUwbTagBleBinding
import java.util.UUID

// ---------------------------------------------------------------------------
// GATT service / characteristic UUIDs
// ---------------------------------------------------------------------------
private val SERVICE_UUID     = UUID.fromString("12345678-1234-5678-1234-56789abcdef0")
private val CHAR_CONFIG_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef1") // read/write JSON config
private val CHAR_STATUS_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef2") // notify: tag status string
private val CHAR_BATT_UUID   = UUID.fromString("12345678-1234-5678-1234-56789abcdef3") // notify: battery %
private val CCCD_UUID        = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")

// BLE scan timeout
private const val SCAN_TIMEOUT_MS = 15_000L

// Permissions request code
private const val REQ_PERMISSIONS = 1001

// ---------------------------------------------------------------------------
// Data model
// ---------------------------------------------------------------------------
data class TagConfig(
    val tag_name: String = "UWB_TAG_0001",
    val sleep_timeout_s: Int = 300,
    val display_brightness: Int = 50,
    val uwb_channel: Int = 9,
    val ranging_interval_ms: Int = 100,
    val battery_report: Boolean = true
)

data class ScannedDevice(
    val name: String,
    val address: String,
    var rssi: Int,
    val device: BluetoothDevice
)

// ---------------------------------------------------------------------------
// RecyclerView adapter for scanned devices
// ---------------------------------------------------------------------------
class DeviceAdapter(
    private val onConnect: (ScannedDevice) -> Unit
) : RecyclerView.Adapter<DeviceAdapter.VH>() {

    private val items = mutableListOf<ScannedDevice>()

    fun update(device: ScannedDevice) {
        val idx = items.indexOfFirst { it.address == device.address }
        if (idx >= 0) {
            items[idx] = device
            notifyItemChanged(idx)
        } else {
            items.add(device)
            notifyItemInserted(items.size - 1)
        }
    }

    fun clear() {
        items.clear()
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_ble_device, parent, false)
        return VH(view)
    }

    override fun onBindViewHolder(holder: VH, position: Int) = holder.bind(items[position])
    override fun getItemCount() = items.size

    inner class VH(view: View) : RecyclerView.ViewHolder(view) {
        private val tvName    = view.findViewById<TextView>(R.id.tvDeviceName)
        private val tvAddress = view.findViewById<TextView>(R.id.tvDeviceAddress)
        private val tvRssi    = view.findViewById<TextView>(R.id.tvRssi)
        private val btnConn   = view.findViewById<Button>(R.id.btnConnect)

        fun bind(item: ScannedDevice) {
            tvName.text    = item.name
            tvAddress.text = item.address
            tvRssi.text    = "${item.rssi} dBm"
            btnConn.setOnClickListener { onConnect(item) }
        }
    }
}

// ---------------------------------------------------------------------------
// Activity
// ---------------------------------------------------------------------------
@SuppressLint("MissingPermission") // permissions checked at runtime before any BLE call
class UwbTagBleActivity : AppCompatActivity() {

    private lateinit var binding: ActivityUwbTagBleBinding
    private val gson = Gson()
    private val mainHandler = Handler(Looper.getMainLooper())

    // BLE
    private val btManager by lazy { getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager }
    private val btAdapter by lazy { btManager.adapter }
    private var bleScanner: BluetoothLeScanner? = null
    private var gatt: BluetoothGatt? = null
    private var configChar: BluetoothGattCharacteristic? = null
    private var statusChar: BluetoothGattCharacteristic? = null
    private var battChar: BluetoothGattCharacteristic? = null
    private var isScanning = false

    private val deviceAdapter = DeviceAdapter(onConnect = ::connectToDevice)

    // ---------------------------------------------------------------------------
    // Lifecycle
    // ---------------------------------------------------------------------------
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityUwbTagBleBinding.inflate(layoutInflater)
        setContentView(binding.root)
        setSupportActionBar(binding.toolbar)

        binding.rvDevices.layoutManager = LinearLayoutManager(this)
        binding.rvDevices.adapter = deviceAdapter

        binding.btnScan.setOnClickListener {
            if (isScanning) stopScan() else startScanIfPermitted()
        }
        binding.btnDisconnect.setOnClickListener { disconnectGatt() }
        binding.btnReadConfig.setOnClickListener { readConfig() }
        binding.btnWriteConfig.setOnClickListener { writeConfig() }

        requestBlePermissions()
    }

    override fun onDestroy() {
        super.onDestroy()
        stopScan()
        disconnectGatt()
    }

    // ---------------------------------------------------------------------------
    // Permissions
    // ---------------------------------------------------------------------------
    private fun requestBlePermissions() {
        val needed = mutableListOf<String>()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            if (!hasPermission(Manifest.permission.BLUETOOTH_SCAN))
                needed += Manifest.permission.BLUETOOTH_SCAN
            if (!hasPermission(Manifest.permission.BLUETOOTH_CONNECT))
                needed += Manifest.permission.BLUETOOTH_CONNECT
        } else {
            if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION))
                needed += Manifest.permission.ACCESS_FINE_LOCATION
        }
        if (needed.isNotEmpty()) {
            ActivityCompat.requestPermissions(this, needed.toTypedArray(), REQ_PERMISSIONS)
        }
    }

    private fun hasPermission(perm: String) =
        ContextCompat.checkSelfPermission(this, perm) == PackageManager.PERMISSION_GRANTED

    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String>, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQ_PERMISSIONS &&
            grantResults.any { it != PackageManager.PERMISSION_GRANTED }) {
            toast("BLE permissions required")
        }
    }

    // ---------------------------------------------------------------------------
    // BLE Scan
    // ---------------------------------------------------------------------------
    private fun startScanIfPermitted() {
        if (btAdapter?.isEnabled != true) { toast("Bluetooth is off"); return }
        bleScanner = btAdapter.bluetoothLeScanner
        val filter = ScanFilter.Builder()
            .setDeviceNamePattern("UWB_TAG_.*".toRegex().toPattern())
            .build()
        val settings = ScanSettings.Builder()
            .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
            .build()
        deviceAdapter.clear()
        bleScanner?.startScan(listOf(filter), settings, scanCallback)
        isScanning = true
        binding.btnScan.text = "Stop"
        binding.tvScanStatus.text = "Scanning…"
        mainHandler.postDelayed({ stopScan() }, SCAN_TIMEOUT_MS)
    }

    private fun stopScan() {
        bleScanner?.stopScan(scanCallback)
        isScanning = false
        binding.btnScan.text = "Scan"
        binding.tvScanStatus.text = "Scan stopped"
    }

    private val scanCallback = object : ScanCallback() {
        override fun onScanResult(callbackType: Int, result: ScanResult) {
            val name = result.device.name ?: return
            if (!name.startsWith("UWB_TAG_")) return
            val dev = ScannedDevice(
                name    = name,
                address = result.device.address,
                rssi    = result.rssi,
                device  = result.device
            )
            mainHandler.post { deviceAdapter.update(dev) }
        }

        override fun onScanFailed(errorCode: Int) {
            mainHandler.post {
                binding.tvScanStatus.text = "Scan failed (code $errorCode)"
                isScanning = false
                binding.btnScan.text = "Scan"
            }
        }
    }

    // ---------------------------------------------------------------------------
    // GATT Connection
    // ---------------------------------------------------------------------------
    private fun connectToDevice(scanned: ScannedDevice) {
        stopScan()
        binding.tvScanStatus.text = "Connecting to ${scanned.name}…"
        gatt = scanned.device.connectGatt(this, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
    }

    private fun disconnectGatt() {
        gatt?.disconnect()
        gatt?.close()
        gatt = null
        configChar = null
        statusChar = null
        battChar = null
        mainHandler.post {
            binding.cardConfig.visibility = View.GONE
            binding.tvScanStatus.text = "Disconnected"
        }
    }

    private val gattCallback = object : BluetoothGattCallback() {

        override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) {
            when (newState) {
                BluetoothProfile.STATE_CONNECTED -> {
                    mainHandler.post { binding.tvScanStatus.text = "Connected — discovering services…" }
                    g.discoverServices()
                }
                BluetoothProfile.STATE_DISCONNECTED -> {
                    mainHandler.post {
                        binding.cardConfig.visibility = View.GONE
                        binding.tvScanStatus.text = "Disconnected"
                        toast("Tag disconnected")
                    }
                    gatt?.close()
                    gatt = null
                }
            }
        }

        override fun onServicesDiscovered(g: BluetoothGatt, status: Int) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                mainHandler.post { toast("Service discovery failed") }
                return
            }
            val service = g.getService(SERVICE_UUID)
            if (service == null) {
                mainHandler.post { toast("UWB config service not found on tag") }
                return
            }
            configChar = service.getCharacteristic(CHAR_CONFIG_UUID)
            statusChar = service.getCharacteristic(CHAR_STATUS_UUID)
            battChar   = service.getCharacteristic(CHAR_BATT_UUID)

            // Subscribe to status notifications
            statusChar?.let { enableNotifications(g, it) }
            battChar?.let   { enableNotifications(g, it) }

            // Initial config read
            configChar?.let { g.readCharacteristic(it) }

            mainHandler.post {
                val devName = g.device.name ?: g.device.address
                binding.tvConnectedName.text = "Connected: $devName"
                binding.cardConfig.visibility = View.VISIBLE
                binding.tvScanStatus.text = "Connected to $devName"
            }
        }

        override fun onCharacteristicRead(
            g: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            if (status != BluetoothGatt.GATT_SUCCESS) return
            if (characteristic.uuid == CHAR_CONFIG_UUID) {
                val json = characteristic.value?.toString(Charsets.UTF_8) ?: return
                val cfg = runCatching { gson.fromJson(json, TagConfig::class.java) }.getOrNull() ?: return
                mainHandler.post { populateFields(cfg) }
            }
        }

        // API 33+ callback
        override fun onCharacteristicRead(
            g: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray,
            status: Int
        ) {
            if (status != BluetoothGatt.GATT_SUCCESS) return
            if (characteristic.uuid == CHAR_CONFIG_UUID) {
                val json = value.toString(Charsets.UTF_8)
                val cfg = runCatching { gson.fromJson(json, TagConfig::class.java) }.getOrNull() ?: return
                mainHandler.post { populateFields(cfg) }
            }
        }

        override fun onCharacteristicWrite(
            g: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            status: Int
        ) {
            val msg = if (status == BluetoothGatt.GATT_SUCCESS) "Config written" else "Write failed ($status)"
            mainHandler.post { toast(msg) }
        }

        override fun onCharacteristicChanged(
            g: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic
        ) {
            val value = characteristic.value ?: return
            handleNotification(characteristic.uuid, value)
        }

        // API 33+ callback
        override fun onCharacteristicChanged(
            g: BluetoothGatt,
            characteristic: BluetoothGattCharacteristic,
            value: ByteArray
        ) {
            handleNotification(characteristic.uuid, value)
        }
    }

    // ---------------------------------------------------------------------------
    // Notification helpers
    // ---------------------------------------------------------------------------
    private fun enableNotifications(g: BluetoothGatt, char: BluetoothGattCharacteristic) {
        g.setCharacteristicNotification(char, true)
        val descriptor = char.getDescriptor(CCCD_UUID) ?: return
        descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
        g.writeDescriptor(descriptor)
    }

    private fun handleNotification(uuid: UUID, value: ByteArray) {
        val text = value.toString(Charsets.UTF_8)
        mainHandler.post {
            when (uuid) {
                CHAR_STATUS_UUID -> binding.tvTagStatus.text = "Status: $text"
                CHAR_BATT_UUID   -> {
                    val pct = text.toIntOrNull() ?: return@post
                    binding.tvTagStatus.text = binding.tvTagStatus.text.toString()
                        .replace(Regex("\\| Batt:.*"), "")
                        .trimEnd() + " | Batt: $pct%"
                }
            }
        }
    }

    // ---------------------------------------------------------------------------
    // Config read / write
    // ---------------------------------------------------------------------------
    private fun readConfig() {
        val g = gatt ?: run { toast("Not connected"); return }
        val c = configChar ?: run { toast("Config char not found"); return }
        g.readCharacteristic(c)
    }

    private fun writeConfig() {
        val g = gatt ?: run { toast("Not connected"); return }
        val c = configChar ?: run { toast("Config char not found"); return }
        val cfg = buildConfigFromFields()
        val json = gson.toJson(cfg)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            g.writeCharacteristic(c, json.toByteArray(Charsets.UTF_8),
                BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
        } else {
            @Suppress("DEPRECATION")
            c.value = json.toByteArray(Charsets.UTF_8)
            @Suppress("DEPRECATION")
            g.writeCharacteristic(c)
        }
    }

    // ---------------------------------------------------------------------------
    // UI helpers
    // ---------------------------------------------------------------------------
    private fun populateFields(cfg: TagConfig) {
        binding.etTagName.setText(cfg.tag_name)
        binding.etSleepTimeout.setText(cfg.sleep_timeout_s.toString())
        binding.etBrightness.setText(cfg.display_brightness.toString())
        binding.etUwbChannel.setText(cfg.uwb_channel.toString())
        binding.etRangingInterval.setText(cfg.ranging_interval_ms.toString())
        binding.switchBatteryReport.isChecked = cfg.battery_report
    }

    private fun buildConfigFromFields() = TagConfig(
        tag_name           = binding.etTagName.text?.toString() ?: "UWB_TAG_0001",
        sleep_timeout_s    = binding.etSleepTimeout.text?.toString()?.toIntOrNull() ?: 300,
        display_brightness = binding.etBrightness.text?.toString()?.toIntOrNull() ?: 50,
        uwb_channel        = binding.etUwbChannel.text?.toString()?.toIntOrNull() ?: 9,
        ranging_interval_ms = binding.etRangingInterval.text?.toString()?.toIntOrNull() ?: 100,
        battery_report     = binding.switchBatteryReport.isChecked
    )

    private fun toast(msg: String) =
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
}
