package com.korancrew.brizzi.hce

import android.content.Context
import android.content.SharedPreferences
import org.json.JSONObject

private data class MetricSnapshot(val total: Long, val byEvent: Map<String, Long>)
data class SecurityIncident(val event: String, val count: Long, val threshold: Long)

/**
 * Pengumpul metrik runtime (in-app, ringan, tanpa raw APDU).
 *
 * Counter disimpan di SharedPreferences dengan key event string uppercase.
 * Digunakan untuk:
 * - observability,
 * - ambang incident,
 * - health snapshot.
 */
class BrizziSecurityMetrics(context: Context) {
    private val prefs: SharedPreferences =
        context.getSharedPreferences("brizzi_hce_security_metrics", Context.MODE_PRIVATE)

    /** Tambah 1 event. Key akan dinormalisasi ke uppercase. */
    fun track(event: String) {
        val key = event.trim().uppercase()
        if (key.isEmpty()) return
        val nextEvent = if (prefs.getLong(key, 0L) + 1L < 1L) 1L else prefs.getLong(key, 0L) + 1L
        val total = prefs.getLong(KEY_TOTAL_EVENTS, 0L) + 1L
        prefs.edit().apply {
            putLong(key, nextEvent)
            putLong(KEY_TOTAL_EVENTS, total)
        }.apply()
    }

    /** Export semua metrik sebagai string JSON. */
    fun reportJson(): String {
        val events = JSONObject().apply {
            val all = prefs.all
            all.entries.sortedBy { it.key }.forEach { (name, value) ->
                val num = when (value) {
                    is Long -> value
                    is Int -> value.toLong()
                    is String -> value.toLongOrNull() ?: 0L
                    else -> 0L
                }
                put(name, num)
            }
        }
        return events.toString()
    }

    /** Rasio sukses = COMMAND_SUCCESS / totalCommand. */
    fun successRate(totalCommand: Long): Double {
        if (totalCommand == 0L) return 0.0
        val ok = getCount(EVENT_COMMAND_SUCCESS)
        return ok.toDouble() / totalCommand.toDouble()
    }

    /** Ringkasan rate sukses/gagal secara human-readable. */
    fun commandVolumeByType(totalCommand: Long): String {
        val success = getCount(EVENT_COMMAND_SUCCESS)
        val fail = if (totalCommand - success < 0L) 0L else totalCommand - success
        val failRate = if (totalCommand == 0L) 0.0 else fail.toDouble() / totalCommand.toDouble()
        return "success=$success fail=$fail failRate=${String.format("%.2f", failRate * 100)}%"
    }

    fun activeIncidents(): List<SecurityIncident> =
        INCIDENT_THRESHOLDS.mapNotNull { (event, threshold) ->
            val count = getCount(event)
            if (count >= threshold) SecurityIncident(event, count, threshold.toLong()) else null
        }.sortedBy { it.event }

    /** Reset semua counter event. */
    fun reset() {
        val editor = prefs.edit()
        for (key in prefs.all.keys) {
            editor.remove(key)
        }
        editor.apply()
    }

    /** Snapshot pendek untuk quick-read dalam log/debug UI. */
    fun snapshot(): String {
        val total = prefs.getLong("TOTAL_EVENTS", 0L)
        val summary = mutableMapOf<String, Long>()
        for ((name, value) in prefs.all) {
            if (name == "TOTAL_EVENTS") continue
            summary[name] = when (value) {
                is Long -> value
                is Int -> value.toLong()
                is String -> value.toLongOrNull() ?: 0L
                else -> 0L
            }
        }
        val snapshot = MetricSnapshot(total, summary)
        return buildString {
            append("total=").append(snapshot.total)
            append(" COMMAND_TOTAL=").append(getCount(EVENT_COMMAND_SUCCESS) + getCount(EVENT_COMMAND_FAIL))
            append(" ")
            snapshot.byEvent.entries.sortedBy { it.key }.forEachIndexed { idx, (name, count) ->
                if (idx > 0) append(", ")
                append(name).append("=").append(count)
            }
        }
    }

    /** Helper baca count dengan key langsung (tanpa normalisasi manual di caller). */
    private fun getCount(event: String): Long {
        return when (val raw = prefs.getLong(event, 0L)) {
            in Long.MIN_VALUE..Long.MAX_VALUE -> raw
            else -> 0L
        }
    }

    /** Baca counter event; selalu uppercase dan fallback 0. */
    fun getCountOrZero(event: String): Long = getCount(event.uppercase())

    /** Ringkas daftar incident aktif (default max 8). */
    fun incidentSummary(limit: Int = 8): String {
        val incidents = activeIncidents().take(limit)
        return if (incidents.isEmpty()) {
            "no_incidents"
        } else {
            incidents.joinToString("|") { "${it.event}:${it.count}/${it.threshold}" }
        }
    }

    /** Health snapshot siap diparsing untuk metric dashboard/OPS. */
    fun exportHealthSnapshot(): String = buildString {
        val commandTotal = getCount(EVENT_COMMAND_TOTAL)
        append("command_total=").append(commandTotal)
        append(" command_success=").append(getCount(EVENT_COMMAND_SUCCESS))
            append(" command_fail=").append(if (commandTotal - getCount(EVENT_COMMAND_SUCCESS) < 0L) 0L else commandTotal - getCount(EVENT_COMMAND_SUCCESS))
        append(" timeout=").append(getCount("SESSION_TIMEOUT"))
        append(" rate_limit=").append(getCount("COMMAND_RATE_LIMIT"))
        append(" replay=").append(getCount("REPLAY_DETECTED"))
        append(" wrong_len=").append(getCount("APDU_PARSE_FAILED"))
        append(" fail_rate=").append(String.format("%.2f", 1.0 - successRate(commandTotal)))
    }

    companion object {
        private const val KEY_TOTAL_EVENTS = "TOTAL_EVENTS"
        private const val EVENT_COMMAND_SUCCESS = "COMMAND_SUCCESS"
        private const val EVENT_COMMAND_FAIL = "COMMAND_FAIL"
        private const val EVENT_COMMAND_TOTAL = "COMMAND_TOTAL"
        private val INCIDENT_THRESHOLDS = mapOf(
            "COMMAND_RATE_LIMIT" to 10L,
            "REPLAY_DETECTED" to 3L,
            "APDU_PARSE_FAILED" to 20L,
            "AUTH_DENIED" to 30L,
            "SESSION_TIMEOUT" to 50L,
            "REPLAY_SENSITIVE_RETRY" to 20L,
            "CONDITIONS_NOT_SATISFIED" to 100L,
        )
    }
}
