package no.svorka.mcms.core

import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put

/** One uploaded ONU firmware image from `GET /files/onu-firmware/`. */
class FirmwareFile(val raw: JsonElement) {
    val id: String get() = raw["_id"].string ?: filename ?: "?"
    val filename: String? get() = raw["filename"].string ?: raw["_id"].string
    val version: String? get() = raw["metadata"]["Version"].string
    val manufacturer: String? get() = raw["metadata"]["Compatible Manufacturer"].string
    val models: List<String> get() = raw["metadata"]["Compatible Model"].array?.mapNotNull { it.string } ?: emptyList()
    val sizeBytes: Double? get() = raw["length"].double
    val uploadDate: String? get() = raw["uploadDate"].string

    /**
     * Fail CLOSED: a file counts as compatible only when we can positively confirm
     * it. The ONU's vendor and equipment id must be known, the file must declare a
     * matching manufacturer, and the equipment id must be an exact (not substring)
     * member of Compatible Model. Unknown identity or missing metadata ⇒ not
     * compatible-by-default (such files are still reachable via "Show all firmware",
     * but staging one requires an explicit extra acknowledgment in the UI).
     * Mis-flashing the wrong image can brick an ONU, so we never guess.
     */
    fun isCompatible(vendor: String?, equipmentId: String?): Boolean {
        if (vendor == null || equipmentId == null) return false
        val mfg = manufacturer ?: return false
        if (!mfg.equals(vendor, ignoreCase = true)) return false
        if (models.isEmpty()) return false
        return models.any { it.equals(equipmentId, ignoreCase = true) }
    }
}

/** Procedure 7 (per-ONU firmware upgrade) as a pure transform. */
object FirmwareUpgrade {
    data class Result(val document: JsonElement, val slot: Int)

    /** Stage `filename`/`version` in the ONU's INACTIVE bank and point at it,
     *  preserving every other field (full-doc PUT). Ptr 0→1, 1→0, else→1. */
    fun apply(config: JsonElement, filename: String, version: String): Result? {
        val root = config as? JsonObject ?: return null
        val onu = root["ONU"] as? JsonObject ?: return null

        val currentPtr = onu["FW Bank Ptr"].int ?: 65535
        val slot = if (currentPtr == 1) 0 else 1

        val files = (onu["FW Bank Files"] as? JsonArray)?.toMutableList() ?: mutableListOf()
        val versions = (onu["FW Bank Versions"] as? JsonArray)?.toMutableList() ?: mutableListOf()
        while (files.size <= slot) files.add(JsonPrimitive(""))
        while (versions.size <= slot) versions.add(JsonPrimitive(""))
        files[slot] = JsonPrimitive(filename)
        versions[slot] = JsonPrimitive(version)

        val newOnu = buildJsonObject {
            onu.forEach { (k, v) -> put(k, v) }
            put("FW Bank Files", JsonArray(files))
            put("FW Bank Versions", JsonArray(versions))
            put("FW Bank Ptr", JsonPrimitive(slot))
        }
        val newRoot = buildJsonObject {
            root.forEach { (k, v) -> put(k, v) }
            put("ONU", newOnu)
        }
        return Result(newRoot, slot)
    }
}
