package no.svorka.mcms.core

import kotlinx.serialization.json.JsonElement

// ───────────────────────── Enums ─────────────────────────

/** ONU registration buckets from OLT-STATE `"ONU States"` (dev guide Procedure 2). */
enum class OnuLifecycleState(val label: String) {
    REGISTERED("Registered"),
    UNSPECIFIED("Unspecified"),
    UNPROVISIONED("Unprovisioned"),
    DEREGISTERED("Deregistered"),
    DYING_GASP("Dying Gasp"),
    DISABLED("Disabled"),
    DISALLOWED_ADMIN("Disallowed Admin"),
    DISALLOWED_ERROR("Disallowed Error"),
    DISALLOWED_REG_ID("Disallowed Reg ID"),
    UNKNOWN("Unknown");

    /** Registered or Unspecified counts as online (dev guide Procedure 2). */
    val isOnline: Boolean get() = this == REGISTERED || this == UNSPECIFIED

    companion object {
        fun fromBucket(name: String): OnuLifecycleState =
            entries.firstOrNull { it.label.equals(name, ignoreCase = true) } ?: UNKNOWN
    }
}

enum class AlarmSeverity(val rank: Int, val label: String) {
    EMERGENCY(0, "emergency"), ALERT(1, "alert"), CRITICAL(2, "critical"),
    ERROR(3, "error"), WARNING(4, "warning"), NOTICE(5, "notice"),
    INFO(6, "info"), DEBUG(7, "debug"), UNKNOWN(8, "unknown");

    companion object {
        fun from(raw: String?): AlarmSeverity =
            entries.firstOrNull { it.label.equals(raw, ignoreCase = true) } ?: UNKNOWN
    }
}

// ───────────────────────── ONU-STATE ─────────────────────────

class OnuStateDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val serialNumber: String? get() = raw["_id"].string
    /** Parent OLT MAC, from `OLT.MAC Address` (dev guide Procedure 2). */
    val parentOlt: String? get() = raw["OLT"]["MAC Address"].string
    val lastUpdate: String? get() = raw["Time"].string

    /** Registration lives on OLT-STATE; resolved + stamped by the list/dashboard VMs. */
    var resolvedLifecycle: OnuLifecycleState? = null
    val lifecycle: OnuLifecycleState get() = resolvedLifecycle ?: OnuLifecycleState.UNKNOWN

    /** Operator name from ONU-CFG; stamped by the VM. */
    var resolvedName: String? = null
    val displayName: String get() = resolvedName?.takeIf { it.isNotEmpty() } ?: id

    private val onu get() = raw["ONU"]
    val registrationState: String? get() = onu["Registration State"].string
    val equipmentId: String? get() = onu["Equipment ID"].string
    val vendor: String? get() = onu["Vendor"].string
    val ponMode: String? get() = onu["PON Mode"].string
    val onlineTime: String? get() = onu["Online Time"].string
    val temperatureC: Double? get() = raw["GPON"]["Temperature"].double

    val fwVersion: String? get() = onu["FW Version"].string
    val fwBankVersions: List<String> get() = onu["FW Bank Versions"].array?.mapNotNull { it.string } ?: emptyList()
    val fwBankPtr: Int? get() = onu["FW Bank Ptr"].int
    val fwUpgradeStatus: JsonElement? get() = onu["FW Upgrade Status"]?.takeIf { it.obj?.isNotEmpty() == true }

    private fun ponStat(pon: String, key: String): Double? = raw["STATS"][pon][key].double
    val rxOpticalDBm: Double? get() = ponStat("ONU-PON", "RX Optical Level")
    val txOpticalDBm: Double? get() = ponStat("ONU-PON", "TX Optical Level")
    val onuPreFec: Double? get() = ponStat("ONU-PON", "RX Pre-FEC BER")
    val onuPostFec: Double? get() = ponStat("ONU-PON", "RX Post-FEC BER")
    val oltPreFec: Double? get() = ponStat("OLT-PON", "RX Pre-FEC BER")
    val oltPostFec: Double? get() = ponStat("OLT-PON", "RX Post-FEC BER")

    val uniPorts: List<UniPort> get() = (raw.obj ?: emptyMap()).entries
        .filter { it.key.startsWith("UNI-") }.sortedBy { it.key }
        .map { UniPort(it.key, it.value) }

    val ponServices: List<PonServiceStats> get() = (raw["STATS"].obj ?: emptyMap()).entries
        .filter { it.key.startsWith("OLT-PON Service ") }.sortedBy { it.key }
        .map { PonServiceStats(it.key, it.value) }
}

class UniPort(val name: String, val raw: JsonElement) {
    val isEthernet: Boolean get() = name.contains("ETH", ignoreCase = true)
    val enabled: Boolean? get() = raw["Enable"].bool
    val speed: String? get() = raw["Speed"].string
    val duplex: String? get() = raw["Duplex"].string
    val state: String? get() = raw["State"].string
}

class PonServiceStats(val name: String, val raw: JsonElement) {
    val rxRateBps: Double? get() = raw["RX Rate bps"].double
    val txRateBps: Double? get() = raw["TX Rate bps"].double
    val rxTotalOctets: Double? get() = raw["RX Total Octets"].double
    val txTotalOctets: Double? get() = raw["TX Total Octets"].double
}

// ───────────────────────── ONU-CFG ─────────────────────────

class OnuConfigDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val name: String? get() = raw["ONU"]["Name"].string
}

// ───────────────────────── OLT-STATE / OLT-CFG ─────────────────────────

class OltStateDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val lastUpdate: String? get() = raw["Time"].string

    var resolvedName: String? = null
    val displayName: String get() = resolvedName?.takeIf { it.isNotEmpty() } ?: id

    val onuStatesByBucket: Map<String, List<String>> get() {
        val o = raw["ONU States"].obj ?: return emptyMap()
        return o.mapValues { (_, v) -> v.array?.mapNotNull { it.string } ?: emptyList() }
    }
    val totalOnus: Int get() = onuStatesByBucket.values.sumOf { it.size }
    val onlineOnus: Int get() = onuStatesByBucket.entries.sumOf { (bucket, ids) ->
        if (OnuLifecycleState.fromBucket(bucket).isOnline) ids.size else 0
    }
    val populatedBuckets: List<Pair<OnuLifecycleState, List<String>>> get() = onuStatesByBucket
        .filterValues { it.isNotEmpty() }
        .map { OnuLifecycleState.fromBucket(it.key) to it.value.sorted() }
        .sortedBy { it.first.ordinal }

    private val olt get() = raw["OLT"]
    private val ponStats get() = raw["STATS"]["OLT-PON"]
    val onlineTime: String? get() = olt["Online Time"].string
    val firmwareVersion: String? get() = olt["FW Version"].string
    val model: String? get() = olt["Model"].string
    val ponMode: String? get() = olt["PON Mode"].string

    val statsTotalOnus: Int? get() = ponStats["Total ONUs Count"].int
    val statsOnlineOnus: Int? get() = ponStats["Online ONUs Count"].int
    val rxUtilPercent: Int? get() = ponStats["RX BW Total Util"].int
    val txUtilPercent: Int? get() = ponStats["TX BW Total Util"].int
    val rxRateBps: Double? get() = ponStats["RX BW Ethernet Rate bps"].double
    val txRateBps: Double? get() = ponStats["TX BW Ethernet Rate bps"].double

    val asicTempC: Int? get() = raw["STATS"]["OLT-TEMP"]["ASIC"].int
    val voltage: Double? get() = raw["STATS"]["OLT-ENV"]["Voltage"].double

    val laserShutdown: String? get() = olt["Laser Shutdown"].string
    val isLaserOff: Boolean get() = laserShutdown?.let { !it.equals("Laser ON", ignoreCase = true) } ?: false

    val switchName: String? get() = raw["Switch"]["System Name"].string?.takeIf { it.isNotEmpty() }
    val switchPort: String? get() = raw["Switch"]["Port ID"].string?.takeIf { it.isNotEmpty() }
    val switchAddress: String? get() = raw["Switch"]["IPv4 Address"].string?.takeIf { it.isNotEmpty() }
}

class OltConfigDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    /** Operator label `OLT.Name` (NOT `NETCONF.Name`, which defaults to the MAC). */
    val name: String? get() = raw["OLT"]["Name"].string?.takeIf { it.isNotEmpty() }
}

class ControllerConfigDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
}

// ───────────────────────── Alarms / logs / stats / CPE ─────────────────────────

class AlarmHistoryDoc(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val severity: AlarmSeverity get() = AlarmSeverity.from(raw["Severity"].string ?: raw["severity"].string)
    val message: String? get() = raw["Message"].string ?: raw["message"].string
    val raisedAt: String? get() = raw["Time"].string ?: raw["timestamp"].string
}

class LogEntry(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val severity: AlarmSeverity get() = AlarmSeverity.from(raw["Severity"].string ?: raw["severity"].string)
    val message: String? get() = raw["Message"].string ?: raw["message"].string
    val time: String? get() = raw["Time"].string ?: raw["timestamp"].string
}

class StatSample(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val timestamp: String? get() = raw["Time"].string ?: raw["timestamp"].string
    val numericMetrics: List<Pair<String, Double>> get() = (raw.obj ?: emptyMap())
        .mapNotNull { (k, v) -> v.double?.let { k to it } }.sortedBy { it.first }
}

/** A CPE behind an ONU with its DHCP lease — from the `/cpe/onu/<id>/` helper. */
class CpeState(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: "?"
    val mac: String? get() = raw["_id"].string

    val dhcpState: String? get() = raw["DHCP"]["Client State"].string?.takeIf { it.isNotEmpty() }
    val ipv4: String? get() = raw["DHCP"]["Client IP Addr"].string?.takeIf { it.isNotEmpty() }
    val dhcpServer: String? get() = raw["DHCP"]["Server ID"].string?.takeIf { it.isNotEmpty() }
    val dhcpLeaseSeconds: Int? get() = raw["DHCP"]["Lease Time"].int
    val circuitId: String? get() = raw["DHCP"]["Circuit ID"].string?.takeIf { it.isNotEmpty() }
    val dhcpLastSuccess: String? get() = raw["DHCP"]["Last Success Time"].string?.takeIf { it.isNotEmpty() }

    val dhcpv6State: String? get() = raw["DHCPV6"]["Client State"].string?.takeIf { it.isNotEmpty() }
    val ipv6: String? get() = raw["DHCPV6"]["Client IP Addr"].string?.takeIf { it.isNotEmpty() }
    val ipv6ValidLifetime: String? get() = raw["DHCPV6"]["Valid Lifetime"].string?.takeIf { it.isNotEmpty() }
    val ipv6Prefix: String? get() {
        val entry = raw["DHCPV6"]["Prefix Delegation"].array?.firstOrNull() ?: return null
        val prefixObj = entry["Prefixes"].array?.firstOrNull() ?: return null
        val prefix = prefixObj["Prefix"].string ?: return null
        val len = prefixObj["Prefix Length"].int ?: return null
        return "$prefix/$len"
    }
    val hasLease: Boolean get() = ipv4 != null || ipv6 != null
}
