package com.tomhempel.rbarometer.scoring

import com.google.gson.JsonArray
import com.google.gson.JsonObject

/**
 * Mirrors [qdb_compute_questionnaire_score] / [qdb_score_points_for_question] on the server.
 */
object QuestionnaireScoreCalculator {

    private val defaultGlassPoints = mapOf(
        "never_glass" to 0,
        "little_glass" to 1,
        "moderate_glass" to 2,
        "much_glass" to 3,
        "extreme_glass" to 4,
    )

    fun compute(questions: JsonArray, answers: Map<String, String>): Int {
        var total = 0
        for (element in questions) {
            if (!element.isJsonObject) continue
            total += scoreQuestion(element.asJsonObject, answers)
        }
        return total
    }

    private fun scoreQuestion(question: JsonObject, answers: Map<String, String>): Int {
        val layout = question.get("layout")?.asString.orEmpty()
        val symptoms = question.getAsJsonArray("symptoms")
        if (layout == "glass_scale_question" && symptoms != null && symptoms.size() > 0) {
            val ruleMap = parseScoreRuleMap(question)
            var sum = 0
            for (symptom in symptoms) {
                val symptomKey = symptom.asString.trim()
                if (symptomKey.isEmpty()) continue
                val levelKey = answers[symptomKey]?.trim().orEmpty()
                if (levelKey.isEmpty()) continue
                sum += ruleMap[symptomKey]?.get(levelKey)
                    ?: ruleMap[""]?.get(levelKey)
                    ?: defaultGlassPoints[levelKey]
                    ?: 0
            }
            return sum
        }

        val stored = resolveStoredAnswer(answers, question) ?: return 0
        val pointsMap = parsePointsMap(question)

        return when (layout) {
            "radio_question", "string_spinner" -> pointsMap[stored] ?: 0
            "multi_check_box_question" -> decodeMultiCheck(stored).sumOf { pointsMap[it] ?: 0 }
            "slider_question", "value_spinner" -> {
                val ruleMap = parseScoreRuleMap(question)
                val levelKey = numericLevelKey(stored)
                ruleMap[""]?.get(levelKey) ?: 0
            }
            else -> 0
        }
    }

    private fun resolveStoredAnswer(answers: Map<String, String>, question: JsonObject): String? {
        val questionKey = question.get("question")?.asString?.takeIf { it.isNotBlank() }
        val shortId = question.get("id")?.asString?.takeIf { it.isNotBlank() }
        if (questionKey != null && questionKey != shortId) {
            return answers[questionKey]?.trim()?.takeIf { it.isNotEmpty() }
        }
        return shortId?.let { answers[it]?.trim()?.takeIf { v -> v.isNotEmpty() } }
    }

    private fun parsePointsMap(question: JsonObject): Map<String, Int> {
        val map = question.getAsJsonObject("pointsMap") ?: return emptyMap()
        return map.entrySet().mapNotNull { (key, value) ->
            if (!value.isJsonPrimitive) return@mapNotNull null
            key to value.asInt
        }.toMap()
    }

    private fun parseScoreRuleMap(question: JsonObject): Map<String, Map<String, Int>> {
        val root = question.getAsJsonObject("scoreRuleMap") ?: return emptyMap()
        val out = mutableMapOf<String, MutableMap<String, Int>>()
        for ((scopeKey, levelsEl) in root.entrySet()) {
            if (!levelsEl.isJsonObject) continue
            val levels = mutableMapOf<String, Int>()
            for ((levelKey, pointsEl) in levelsEl.asJsonObject.entrySet()) {
                if (pointsEl.isJsonPrimitive) {
                    levels[levelKey] = pointsEl.asInt
                }
            }
            out[scopeKey] = levels
        }
        return out
    }

    private fun numericLevelKey(raw: String): String {
        val trimmed = raw.trim()
        val asDouble = trimmed.toDoubleOrNull() ?: return trimmed
        return kotlin.math.round(asDouble).toInt().toString()
    }

    /** Mirrors [qdb_decode_multi_check_values]. */
    fun decodeMultiCheck(raw: String): List<String> {
        val trimmed = raw.trim()
        if (trimmed.isEmpty()) return emptyList()
        if (trimmed.startsWith("[")) {
            val decoded = runCatching {
                com.google.gson.JsonParser.parseString(trimmed).asJsonArray
            }.getOrNull()
            if (decoded != null) {
                return decoded.mapNotNull { el ->
                    el.takeIf { it.isJsonPrimitive }?.asString?.trim()?.takeIf { it.isNotEmpty() }
                }
            }
        }
        val sep = when {
            trimmed.contains(',') -> ','
            trimmed.contains(';') -> ';'
            else -> null
        }
        if (sep != null) {
            return trimmed.split(sep)
                .map { it.trim().trim('"', '\'') }
                .filter { it.isNotEmpty() }
        }
        return listOf(trimmed)
    }
}
