package no.svorka.mcms.ui

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import no.svorka.mcms.core.*

// ───────────────────────── List ─────────────────────────

class OnuListModel(private val connection: McmsConnection) {
    var phase by mutableStateOf<LoadPhase>(LoadPhase.Idle)
    var onus by mutableStateOf<List<OnuStateDoc>>(emptyList())
    var search by mutableStateOf("")

    val filtered: List<OnuStateDoc>
        get() {
            val q = search.trim()
            if (q.isEmpty()) return onus
            return onus.filter {
                it.id.contains(q, true) ||
                    (it.resolvedName?.contains(q, true) == true) ||
                    (it.parentOlt?.contains(q, true) == true)
            }
        }

    private var loadGen = 0

    suspend fun load() {
        val gen = ++loadGen
        phase = LoadPhase.Loading
        try {
            val fetched = connection.onu.allStates()
            val reg = runCatching { connection.olt.registrationMap() }.getOrDefault(emptyMap())
            val names = runCatching { connection.onu.nameMap() }.getOrDefault(emptyMap())
            fetched.forEach { it.resolvedLifecycle = reg[it.id]; it.resolvedName = names[it.id] }
            val sorted = fetched.sortedBy { it.displayName.lowercase() }
            if (gen != loadGen) return
            onus = sorted
            phase = LoadPhase.Loaded
        } catch (e: CancellationException) {
            throw e
        } catch (e: Throwable) {
            if (gen == loadGen) phase = LoadPhase.Failed(e.uiMessage())
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnuListScreen(connection: McmsConnection, openOnu: (String) -> Unit) {
    val model = remember(connection) { OnuListModel(connection) }
    val scope = rememberCoroutineScope()
    LaunchedEffect(connection) { if (model.phase is LoadPhase.Idle) model.load() }

    Scaffold(topBar = { TopAppBar(title = { Text("ONUs") }) }) { padding ->
        Column(Modifier.padding(padding).fillMaxSize()) {
            OutlinedTextField(
                value = model.search, onValueChange = { model.search = it },
                placeholder = { Text("Name, serial, or OLT") }, singleLine = true,
                modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp),
            )
            when (val p = model.phase) {
                is LoadPhase.Idle, is LoadPhase.Loading -> if (model.onus.isEmpty()) LoadingView("Loading ONUs…") else OnuList(model.filtered, openOnu)
                is LoadPhase.Failed -> ErrorState(p.message) { scope.launch { model.load() } }
                is LoadPhase.Loaded -> OnuList(model.filtered, openOnu)
            }
        }
    }
}

@Composable
private fun OnuList(onus: List<OnuStateDoc>, openOnu: (String) -> Unit) {
    LazyColumn(Modifier.fillMaxSize()) {
        items(onus) { onu ->
            ListRow(onClick = { openOnu(onu.id) }) {
                Column(Modifier.weight(1f)) {
                    Text(onu.displayName, fontWeight = FontWeight.Medium)
                    subtitle(onu)?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
                }
                StateBadge(onu.lifecycle.label, onu.lifecycle.color())
            }
            HorizontalDivider()
        }
    }
}

private fun subtitle(onu: OnuStateDoc): String? {
    val parts = buildList {
        if (onu.resolvedName != null) add(onu.id)
        onu.parentOlt?.let { add("OLT $it") }
    }
    return parts.joinToString(" · ").ifEmpty { null }
}

// ───────────────────────── Detail ─────────────────────────

private val prettyJson = Json { prettyPrint = true }

class OnuDetailModel(private val connection: McmsConnection, val onuId: String) {
    var tab by mutableStateOf(0)   // 0 overview · 1 cpe · 2 logs · 3 config
    var phase by mutableStateOf<LoadPhase>(LoadPhase.Idle)
    var liveState by mutableStateOf<OnuStateDoc?>(null)
    var cpes by mutableStateOf<List<CpeState>>(emptyList())
    var logs by mutableStateOf<List<LogEntry>>(emptyList())
    var config by mutableStateOf<OnuConfigDoc?>(null)
    var firmwareFiles by mutableStateOf<List<FirmwareFile>>(emptyList())
    var firmwareError by mutableStateOf<String?>(null)
    var actionMessage by mutableStateOf<String?>(null)
    var busy by mutableStateOf(false)

    // Monotonic load id so a slow fetch for a tab the user already left can't
    // clobber the newer tab's phase/data.
    private var loadGen = 0

    suspend fun loadTab() { phase = LoadPhase.Loading; reload() }
    suspend fun refresh() { reload() }
    private suspend fun reload() {
        val gen = ++loadGen
        try {
            when (tab) {
                0 -> { val v = connection.onu.state(onuId); if (gen != loadGen) return; liveState = v }
                1 -> { val v = connection.onu.cpes(onuId); if (gen != loadGen) return; cpes = v }
                2 -> { val v = connection.onu.logs(onuId); if (gen != loadGen) return; logs = v }
                3 -> { val v = connection.onu.config(onuId); if (gen != loadGen) return; config = v }
            }
            if (gen != loadGen) return
            phase = LoadPhase.Loaded
        } catch (e: CancellationException) {
            throw e
        } catch (e: Throwable) {
            if (gen == loadGen) phase = LoadPhase.Failed(e.uiMessage())
        }
    }

    suspend fun loadFirmware() {
        firmwareError = null
        try { firmwareFiles = connection.onu.firmwareFiles() }
        catch (e: CancellationException) { throw e }
        catch (e: Throwable) { firmwareError = e.uiMessage() }
    }

    suspend fun reset() {
        if (busy) return
        busy = true; actionMessage = null
        try { connection.onu.reset(onuId); actionMessage = "Reset requested." }
        catch (e: CancellationException) { throw e }
        catch (e: Throwable) { actionMessage = e.uiMessage() }
        finally { busy = false }
    }

    suspend fun upgrade(file: FirmwareFile) {
        if (busy) return
        busy = true; actionMessage = null
        try {
            val slot = connection.onu.upgradeFirmware(onuId, file)
            actionMessage = "Firmware ${file.version ?: "image"} staged to bank $slot. The ONU starts it after its next reboot."
        }
        catch (e: CancellationException) { throw e }
        catch (e: Throwable) { actionMessage = e.uiMessage() }
        finally { busy = false }
    }
}

private val onuTabs = listOf("Overview", "CPE", "Logs", "Config")

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OnuDetailScreen(connection: McmsConnection, onuId: String, onBack: () -> Unit) {
    val model = remember(onuId) { OnuDetailModel(connection, onuId) }
    val scope = rememberCoroutineScope()
    var showReset by remember { mutableStateOf(false) }
    var showFirmware by remember { mutableStateOf(false) }
    var menu by remember { mutableStateOf(false) }

    LaunchedEffect(model.tab) { model.loadTab() }

    Scaffold(topBar = {
        TopAppBar(
            title = { Text(onuId) },
            navigationIcon = { BackButton(onBack) },
            actions = {
                IconButton(onClick = { menu = true }) { Icon(Icons.Filled.MoreVert, "Actions") }
                DropdownMenu(expanded = menu, onDismissRequest = { menu = false }) {
                    DropdownMenuItem(text = { Text("Reset ONU", color = MaterialTheme.colorScheme.error) },
                        enabled = !model.busy, onClick = { menu = false; showReset = true })
                }
            },
        )
    }) { padding ->
        Column(Modifier.padding(padding).fillMaxSize()) {
            PrimaryTabRow(selectedTabIndex = model.tab) {
                onuTabs.forEachIndexed { i, title ->
                    Tab(selected = model.tab == i, onClick = { model.tab = i }, text = { Text(title) })
                }
            }
            Box(Modifier.fillMaxSize()) {
                when (val p = model.phase) {
                    is LoadPhase.Loading, is LoadPhase.Idle -> LoadingView()
                    is LoadPhase.Failed -> ErrorState(p.message) { scope.launch { model.loadTab() } }
                    is LoadPhase.Loaded -> when (model.tab) {
                        0 -> OverviewTab(model) { showFirmware = true }
                        1 -> CpeTab(model.cpes)
                        2 -> LogsTab(model.logs)
                        else -> ConfigTab(model.config)
                    }
                }
            }
        }
    }

    model.actionMessage?.let { msg ->
        AlertDialog(
            onDismissRequest = { model.actionMessage = null },
            confirmButton = { TextButton(onClick = { model.actionMessage = null }) { Text("OK") } },
            title = { Text("Operation") }, text = { Text(msg) },
        )
    }
    if (showReset) ResetConfirmDialog("ONU", onuId, onDismiss = { showReset = false }) { scope.launch { model.reset() } }
    if (showFirmware) FirmwareDialog(model, onDismiss = { showFirmware = false })
}

@Composable
private fun OverviewTab(model: OnuDetailModel, onUpdateFirmware: () -> Unit) {
    val s = model.liveState ?: return
    LazyColumn(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        item {
            SectionCard("Status") {
                s.registrationState?.let { KeyValueRow("Registration", it) }
                s.equipmentId?.let { KeyValueRow("Equipment", it) }
                s.ponMode?.let { KeyValueRow("PON mode", it) }
                s.temperatureC?.let { KeyValueRow("Temperature", "%.1f °C".format(it)) }
                s.onlineTime?.let {
                    KeyValueRow("Online since", MongoTimestamp.displayString(it) ?: it)
                    MongoTimestamp.duration(it, s.lastUpdate)?.let { up -> KeyValueRow("Uptime", up) }
                }
            }
        }
        item {
            SectionCard("Firmware") {
                s.fwVersion?.let { KeyValueRow("Active version", it) }
                s.fwBankVersions.forEachIndexed { i, v ->
                    KeyValueRow("Bank $i" + if (s.fwBankPtr == i) " · active" else "", v.ifEmpty { "—" })
                }
                Spacer(Modifier.height(8.dp))
                Button(onClick = onUpdateFirmware, enabled = !model.busy) { Text("Update firmware…") }
            }
        }
        item {
            SectionCard("Live optical") {
                OpticalRow("RX optical", s.rxOpticalDBm, green = -28.0, warn = -30.0)
                OpticalRow("TX optical", s.txOpticalDBm, green = 3.0, warn = 3.0)
                FecRow("ONU post-FEC", s.onuPostFec, isPost = true)
                FecRow("OLT post-FEC", s.oltPostFec, isPost = true)
            }
        }
        if (s.uniPorts.isNotEmpty()) {
            item {
                SectionCard("UNI ports") {
                    s.uniPorts.forEach { port ->
                        Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), verticalAlignment = Alignment.CenterVertically) {
                            Column(Modifier.weight(1f)) {
                                Text(port.name)
                                (port.state ?: listOfNotNull(port.speed, port.duplex).joinToString(" · ").ifEmpty { null })
                                    ?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
                            }
                            port.enabled?.let { StateBadge(if (it) "enabled" else "disabled", if (it) Color(0xFF2E7D32) else Color(0xFF757575)) }
                        }
                    }
                }
            }
        }
        if (s.ponServices.isNotEmpty()) {
            item {
                SectionCard("Traffic") {
                    s.ponServices.forEach { svc ->
                        Text(svc.name, fontWeight = FontWeight.Medium)
                        Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
                            Text("↓ ${Format.bps(svc.rxRateBps)}")
                            Text("↑ ${Format.bps(svc.txRateBps)}")
                        }
                        Spacer(Modifier.height(6.dp))
                    }
                }
            }
        }
    }
}

@Composable
private fun OpticalRow(title: String, value: Double?, green: Double, warn: Double) {
    Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), horizontalArrangement = Arrangement.SpaceBetween) {
        Text(title)
        val color = when {
            value == null -> MaterialTheme.colorScheme.onSurfaceVariant
            value >= green -> Color(0xFF2E7D32)
            value >= warn -> Color(0xFFF9A825)
            else -> Color(0xFFC62828)
        }
        Text(value?.let { "%.2f dBm".format(it) } ?: "—", color = color)
    }
}

@Composable
private fun FecRow(title: String, value: Double?, isPost: Boolean) {
    Row(Modifier.fillMaxWidth().padding(vertical = 4.dp), horizontalArrangement = Arrangement.SpaceBetween) {
        Text(title)
        val color = when {
            value == null -> MaterialTheme.colorScheme.onSurfaceVariant
            isPost && value > 0 -> Color(0xFFC62828)
            value > 0 -> MaterialTheme.colorScheme.onSurfaceVariant
            else -> Color(0xFF2E7D32)
        }
        Text(value?.let { it.toLong().toString() } ?: "—", color = color)
    }
}

@Composable
private fun CpeTab(cpes: List<CpeState>) {
    if (cpes.isEmpty()) { EmptyState("No CPEs"); return }
    LazyColumn(Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
        items(cpes) { cpe ->
            SectionCard(cpe.mac ?: cpe.id) {
                cpe.ipv4?.let {
                    KeyValueRow("IPv4", it + (cpe.dhcpState?.let { s -> "  ($s)" } ?: ""))
                    cpe.dhcpServer?.let { s -> KeyValueRow("DHCP server", s) }
                    cpe.dhcpLeaseSeconds?.let { l -> KeyValueRow("Lease", "${l / 3600}h") }
                }
                cpe.ipv6?.let {
                    KeyValueRow("IPv6", it)
                    cpe.ipv6Prefix?.let { p -> KeyValueRow("Delegated prefix", p) }
                }
                cpe.circuitId?.let { KeyValueRow("Circuit ID", it) }
                if (!cpe.hasLease) Text("No active lease.", color = MaterialTheme.colorScheme.onSurfaceVariant)
            }
        }
    }
}

@Composable
private fun LogsTab(logs: List<LogEntry>) {
    if (logs.isEmpty()) { EmptyState("No logs"); return }
    LazyColumn(Modifier.fillMaxSize()) {
        items(logs) { log ->
            Column(Modifier.fillMaxWidth().padding(16.dp, 8.dp)) {
                Row(verticalAlignment = Alignment.CenterVertically) {
                    StateBadge(log.severity.label, log.severity.color())
                    Spacer(Modifier.weight(1f))
                    MongoTimestamp.displayString(log.time)?.let { Text(it, style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
                }
                Text(log.message ?: log.id, style = MaterialTheme.typography.bodySmall)
            }
            HorizontalDivider()
        }
    }
}

@Composable
private fun ConfigTab(config: OnuConfigDoc?) {
    if (config == null) { EmptyState("No configuration"); return }
    Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(16.dp)) {
        config.name?.let { KeyValueRow("Name", it); Spacer(Modifier.height(8.dp)) }
        SelectionContainer {
            Text(prettyJson.encodeToString(JsonElement.serializer(), config.raw),
                fontFamily = FontFamily.Monospace, style = MaterialTheme.typography.bodySmall)
        }
    }
}

@Composable
private fun EmptyState(text: String) {
    Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Text(text, color = MaterialTheme.colorScheme.onSurfaceVariant)
    }
}

// ───────────────────────── Reset + Firmware dialogs ─────────────────────────

@Composable
fun ResetConfirmDialog(kind: String, name: String, onDismiss: () -> Unit, onConfirm: () -> Unit) {
    var ack by remember { mutableStateOf(false) }
    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text("Reset $kind?") },
        text = {
            Column {
                Text("Resetting $kind “$name” reboots it and interrupts service for everything connected to it. This can't be undone.")
                Spacer(Modifier.height(12.dp))
                Row(verticalAlignment = Alignment.CenterVertically) {
                    Checkbox(checked = ack, onCheckedChange = { ack = it })
                    Text("I understand this will interrupt service")
                }
            }
        },
        confirmButton = {
            TextButton(enabled = ack, onClick = { onConfirm(); onDismiss() }) {
                Text("Reset $kind", color = MaterialTheme.colorScheme.error)
            }
        },
        dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } },
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun FirmwareDialog(model: OnuDetailModel, onDismiss: () -> Unit) {
    val scope = rememberCoroutineScope()
    var showAll by remember { mutableStateOf(false) }
    var pending by remember { mutableStateOf<FirmwareFile?>(null) }
    val state = model.liveState
    LaunchedEffect(Unit) { if (model.firmwareFiles.isEmpty()) model.loadFirmware() }

    val compatible = model.firmwareFiles.filter { it.isCompatible(state?.vendor, state?.equipmentId) }
    val visible = if (showAll) model.firmwareFiles else compatible

    AlertDialog(
        onDismissRequest = onDismiss,
        title = { Text("Update firmware") },
        text = {
            Column {
                state?.fwVersion?.let { Text("Current: $it", style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant) }
                Spacer(Modifier.height(8.dp))
                when {
                    model.firmwareError != null -> Text(model.firmwareError!!, color = MaterialTheme.colorScheme.error)
                    model.firmwareFiles.isEmpty() -> Row(verticalAlignment = Alignment.CenterVertically) { CircularProgressIndicator(Modifier.size(18.dp), strokeWidth = 2.dp); Spacer(Modifier.width(8.dp)); Text("Loading…") }
                    else -> Column {
                        visible.forEach { file ->
                            ListRow(onClick = { pending = file }) {
                                Column(Modifier.weight(1f)) {
                                    Text(file.version ?: file.filename ?: "Unknown", fontWeight = FontWeight.Medium)
                                    Text(listOfNotNull(file.models.firstOrNull(), file.sizeBytes?.let { Format.bytes(it) }).joinToString(" · "),
                                        style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.onSurfaceVariant)
                                }
                            }
                        }
                        if (model.firmwareFiles.size > compatible.size) {
                            Row(verticalAlignment = Alignment.CenterVertically) {
                                Switch(checked = showAll, onCheckedChange = { showAll = it }); Spacer(Modifier.width(8.dp)); Text("Show all firmware")
                            }
                        }
                    }
                }
            }
        },
        confirmButton = {},
        dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } },
    )

    pending?.let { file ->
        val isCompatible = file.isCompatible(state?.vendor, state?.equipmentId)
        var ack by remember(file) { mutableStateOf(false) }
        AlertDialog(
            onDismissRequest = { pending = null },
            title = { Text("Stage firmware?") },
            text = {
                Column {
                    Text("Stage ${file.version ?: "this image"} on ${model.onuId} in the standby bank. It becomes active after the ONU's next reboot — this is service-affecting.")
                    if (!isCompatible) {
                        Spacer(Modifier.height(12.dp))
                        Text(
                            "⚠ This image is not confirmed compatible with this ONU (vendor/model mismatch or missing metadata). Flashing an incompatible image can leave the ONU unusable.",
                            color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall,
                        )
                        Row(verticalAlignment = Alignment.CenterVertically) {
                            Checkbox(checked = ack, onCheckedChange = { ack = it })
                            Text("I understand this image may be incompatible")
                        }
                    }
                }
            },
            confirmButton = {
                TextButton(
                    enabled = isCompatible || ack,
                    onClick = { pending = null; onDismiss(); scope.launch { model.upgrade(file) } },
                ) {
                    Text("Download ${file.version ?: "firmware"}", color = MaterialTheme.colorScheme.error)
                }
            },
            dismissButton = { TextButton(onClick = { pending = null }) { Text("Cancel") } },
        )
    }
}
