package app.chompass.services.grounding

import app.chompass.models.RecognizedFoodComponent
import app.chompass.models.ServingUnitHeuristics
import java.util.Locale
import kotlin.math.roundToInt

/**
 * Deterministic portion resolution for grounded entry.
 *
 * Precedence:
 * 1. Explicit gram override (user correction UI / finalize grams)
 * 2. quantity × unit using USDA/OFF serving, then household defaults / heuristics
 * 3. Recognized estimatedGrams (only when qty/unit/hint cannot resolve)
 * 4. Selected candidate servingSizeGrams
 * 5. Name heuristic default
 * 6. Unresolved (null) — never silently invent 100 g
 */
object PortionResolver {
    enum class Source {
        OVERRIDE,
        ESTIMATED_GRAMS,
        QUANTITY_UNIT,
        CANDIDATE_SERVING,
        HEURISTIC,
        UNRESOLVED,
    }

    data class Result(
        val grams: Double?,
        val source: Source,
        val evidence: String?,
        val needsUserConfirmation: Boolean,
    ) {
        val isResolved: Boolean get() = grams != null && grams > 0
    }

    private val VAGUE_UNITS = setOf(
        "slice", "slices", "piece", "pieces", "large", "medium", "small",
    )

    /** Canonical household unit → grams. Aliases normalize via [canonicalUnit]. */
    private val HOUSEHOLD_GRAMS = mapOf(
        "g" to 1.0,
        "gram" to 1.0,
        "grams" to 1.0,
        "kg" to 1000.0,
        "ml" to 1.0,
        "l" to 1000.0,
        "liter" to 1000.0,
        "litre" to 1000.0,
        "oz" to 28.35,
        "ounce" to 28.35,
        "ounces" to 28.35,
        "lb" to 453.6,
        "pound" to 453.6,
        "tbsp" to 15.0,
        "tablespoon" to 15.0,
        "tablespoons" to 15.0,
        "tsp" to 5.0,
        "teaspoon" to 5.0,
        "teaspoons" to 5.0,
        "cup" to 240.0,
        "cups" to 240.0,
        "can" to 330.0,
        "cans" to 330.0,
        "glass" to 240.0,
        "glasses" to 240.0,
        "scoop" to 30.0,
        "scoops" to 30.0,
        "slice" to 30.0,
        "slices" to 30.0,
        "piece" to 50.0,
        "pieces" to 50.0,
        "bar" to 50.0,
        "bars" to 50.0,
        "large" to 50.0,
        "medium" to 118.0,
        "small" to 80.0,
    )

    /** Map common aliases / plurals to a key present in [HOUSEHOLD_GRAMS]. */
    private fun canonicalUnit(unit: String): String {
        val u = unit.trim().lowercase(Locale.US)
        return when (u) {
            "tbs", "tbl", "tbls", "tablespoon", "tablespoons" -> "tbsp"
            "teaspoon", "teaspoons" -> "tsp"
            "ounce", "ounces" -> "oz"
            "pound", "pounds", "lbs" -> "lb"
            "gram", "grams" -> "g"
            "liter", "litre", "liters", "litres" -> "l"
            else -> u
        }
    }

    private fun unitsMatch(a: String, b: String): Boolean {
        val ca = canonicalUnit(a)
        val cb = canonicalUnit(b)
        if (ca == cb) return true
        if (ca.contains(cb) || cb.contains(ca)) return true
        if (ca.removeSuffix("s") == cb.removeSuffix("s")) return true
        return false
    }

    fun resolve(
        component: RecognizedFoodComponent,
        gramOverride: Double? = null,
        candidateServingGrams: Double? = null,
        candidateServingUnit: String? = null,
    ): Result {
        if (gramOverride != null && gramOverride > 0) {
            return Result(gramOverride, Source.OVERRIDE, "user_override=${gramOverride}g", false)
        }

        val qty = component.quantity
        val unitRaw = component.unit?.trim()?.lowercase(Locale.US)
        if (qty != null && qty > 0 && !unitRaw.isNullOrBlank()) {
            val unit = canonicalUnit(unitRaw)
            val fromCandidate = unitGramsFromCandidate(unit, candidateServingUnit, candidateServingGrams)
            if (fromCandidate != null) {
                return Result(
                    grams = qty * fromCandidate,
                    source = Source.QUANTITY_UNIT,
                    evidence = "quantity=${qty}×${unit} via candidate serving ${fromCandidate}g",
                    needsUserConfirmation = unit in VAGUE_UNITS || canonicalUnit(unitRaw) in VAGUE_UNITS,
                )
            }
            HOUSEHOLD_GRAMS[unit]?.let { per ->
                return Result(
                    grams = qty * per,
                    source = Source.QUANTITY_UNIT,
                    evidence = "quantity=${qty}×${unit} household ${per}g",
                    needsUserConfirmation = unit in VAGUE_UNITS,
                )
            }
            ServingUnitHeuristics.matchingRule(component.name)?.let { rule ->
                if (unitsMatch(rule.unit, unit) || unit in setOf("piece", "pieces", "slice", "slices")) {
                    return Result(
                        grams = qty * rule.defaultGramsPerUnit,
                        source = Source.HEURISTIC,
                        evidence = "heuristic ${rule.id}: ${qty}×${rule.defaultGramsPerUnit}g",
                        needsUserConfirmation = true,
                    )
                }
            }
        }

        // Unit-only heuristics: "1 large egg" style via portion_hint quantity parse.
        parseHintQuantity(component.portionHint)?.let { (hintQty, hintUnitRaw) ->
            val hintUnit = canonicalUnit(hintUnitRaw)
            val gramsPer = unitGramsFromCandidate(hintUnit, candidateServingUnit, candidateServingGrams)
                ?: HOUSEHOLD_GRAMS[hintUnit]
                ?: ServingUnitHeuristics.matchingRule(component.name)?.defaultGramsPerUnit
            if (gramsPer != null) {
                return Result(
                    grams = hintQty * gramsPer,
                    source = Source.HEURISTIC,
                    evidence = "portion_hint ${hintQty}×${hintUnit}",
                    needsUserConfirmation = true,
                )
            }
        }

        // estimatedGrams only after qty/unit/hint failed — bad model grams must not
        // override an explicit household quantity.
        component.estimatedGrams?.takeIf { it > 0 }?.let {
            return Result(it, Source.ESTIMATED_GRAMS, "estimated_grams=$it", false)
        }

        candidateServingGrams?.takeIf { it > 0 }?.let {
            return Result(
                grams = it,
                source = Source.CANDIDATE_SERVING,
                evidence = "candidate_serving=${it}g",
                needsUserConfirmation = true,
            )
        }

        ServingUnitHeuristics.matchingRule(component.name)?.let { rule ->
            return Result(
                grams = rule.defaultGramsPerUnit,
                source = Source.HEURISTIC,
                evidence = "heuristic default ${rule.id}=${rule.defaultGramsPerUnit}g",
                needsUserConfirmation = true,
            )
        }

        return Result(
            grams = null,
            source = Source.UNRESOLVED,
            evidence = component.portionHint ?: "portion unresolved",
            needsUserConfirmation = true,
        )
    }

    fun gramsOrNull(result: Result): Double? = result.grams?.takeIf { it > 0 }

    fun formatEvidence(result: Result): String =
        result.evidence ?: result.source.name.lowercase(Locale.US)

    private fun unitGramsFromCandidate(
        unit: String,
        candidateUnit: String?,
        candidateGrams: Double?,
    ): Double? {
        if (candidateGrams == null || candidateGrams <= 0) return null
        val cu = candidateUnit?.trim()?.lowercase(Locale.US) ?: return null
        if (unitsMatch(unit, cu)) return candidateGrams
        return null
    }

    private fun parseHintQuantity(hint: String?): Pair<Double, String>? {
        if (hint.isNullOrBlank()) return null
        val m = Regex("""(?i)\b(\d+(?:\.\d+)?)\s*([a-zA-Z]+)""").find(hint.trim()) ?: return null
        val qty = m.groupValues[1].toDoubleOrNull() ?: return null
        val unit = m.groupValues[2].lowercase(Locale.US)
        if (qty <= 0) return null
        return qty to unit
    }

    fun roundGrams(grams: Double): Double = (grams * 10.0).roundToInt() / 10.0
}
