package app.voltplan.cable.calc

import app.voltplan.cable.data.model.SavedBattery
import app.voltplan.cable.data.model.SavedCharger
import app.voltplan.cable.data.model.SavedLoad
import app.voltplan.cable.data.model.effectivePowerWatts
import app.voltplan.cable.data.model.energyWattHours
import app.voltplan.cable.data.model.usableCapacityAmpHours
import app.voltplan.cable.data.model.usableEnergyWattHours
import kotlin.math.abs
import kotlin.math.roundToInt

/** Battery-bank mismatch warning. Mirrors the iOS overview/battery-bank logic. */
sealed interface BankWarning {
    data class Voltage(val count: Int, val baseline: Double) : BankWarning
    data class Capacity(val count: Int, val baseline: Double) : BankWarning
}

/** Aggregated system metrics. All formulas ported from `SystemOverviewView`. */
class SystemMetrics(
    val loads: List<SavedLoad>,
    val batteries: List<SavedBattery>,
    val chargers: List<SavedCharger>,
) {
    val totalPower: Double = loads.sumOf { maxOf(it.power, 0.0) }
    val totalCurrent: Double = loads.sumOf { maxOf(it.current, 0.0) }

    val totalCapacity: Double = batteries.sumOf { it.capacityAmpHours }
    val totalUsableCapacity: Double = batteries.sumOf { it.usableCapacityAmpHours }
    val totalEnergy: Double = batteries.sumOf { it.energyWattHours }
    val totalUsableEnergy: Double = batteries.sumOf { it.usableEnergyWattHours }

    val totalChargerCurrent: Double = chargers.sumOf { maxOf(it.maxCurrentAmps, 0.0) }
    val totalChargerPower: Double = chargers.sumOf { maxOf(it.effectivePowerWatts, 0.0) }
    val representativeChargerOutput: Double? = chargers.map { it.outputVoltage }.filter { it > 0 }
        .takeIf { it.isNotEmpty() }?.average()

    /** Average continuous draw, weighting each load by duty cycle and daily on-time. */
    val totalAverageLoadPower: Double = loads.sumOf { load ->
        val power = maxOf(load.power, 0.0)
        if (power <= 0) 0.0 else {
            val duty = load.dutyCyclePercent.coerceIn(0.0, 100.0) / 100.0
            val usage = load.dailyUsageHours.coerceIn(0.0, 24.0) / 24.0
            power * duty * usage
        }
    }

    val estimatedRuntimeHours: Double? =
        ratio(totalUsableEnergy, totalAverageLoadPower)

    val estimatedChargeHours: Double? =
        ratio(totalUsableCapacity, totalChargerCurrent)

    private fun ratio(numerator: Double, denominator: Double): Double? {
        if (numerator.isFinite() && denominator.isFinite() && numerator > 0 && denominator > 0) {
            return numerator / denominator
        }
        return null
    }

    val bankWarning: BankWarning? by lazy {
        if (batteries.size < 2) return@lazy null
        // Voltage mismatch (binned to 0.1V).
        val vBaseline = dominant(batteries.map { it.nominalVoltage }, 0.1)
        val vCount = batteries.count { abs(it.nominalVoltage - vBaseline) > 0.05 }
        if (vCount > 0) return@lazy BankWarning.Voltage(vCount, vBaseline)
        // Capacity mismatch (binned to 1.0 Ah).
        val cBaseline = dominant(batteries.map { it.capacityAmpHours }, 1.0)
        val cCount = batteries.count { abs(it.capacityAmpHours - cBaseline) > 0.5 }
        if (cCount > 0) return@lazy BankWarning.Capacity(cCount, cBaseline)
        null
    }

    private fun dominant(values: List<Double>, bin: Double): Double {
        val counts = HashMap<Int, Int>()
        for (v in values) {
            val key = (v / bin).roundToInt()
            counts[key] = (counts[key] ?: 0) + 1
        }
        val bestKey = counts.maxByOrNull { it.value }?.key ?: 0
        return bestKey * bin
    }

    // --- Bill of Materials completion ---

    private val settledLoads = loads.filter { it.length > 0 && it.crossSection > 0 }
    val bomItemsCount: Int = settledLoads.size * 5 + batteries.size * 1 + chargers.size * 1

    val completedBomItemCount: Int = run {
        val loadDone = settledLoads.sumOf { minOf(it.bomCompletedItemIDs.toSet().size, 5) }
        val battDone = batteries.sumOf { minOf(it.bomCompletedItemIDs.toSet().size, 1) }
        val chgDone = chargers.sumOf { minOf(it.bomCompletedItemIDs.toSet().size, 1) }
        loadDone + battDone + chgDone
    }

    val bomCompletionFraction: Double? =
        if (bomItemsCount > 0) completedBomItemCount.toDouble() / bomItemsCount else null

    val loadsMissingDetails: Int = loads.count { it.length <= 0 || it.crossSection <= 0 }
}

/** Formats a duration in hours as an abbreviated "Xd Yh" / "Yh Zm" string (max 2 units). */
fun formatDurationHours(hours: Double): String {
    if (!hours.isFinite() || hours <= 0) return "—"
    val totalMinutes = (hours * 60).roundToInt()
    val days = totalMinutes / (24 * 60)
    val remAfterDays = totalMinutes % (24 * 60)
    val hrs = remAfterDays / 60
    val mins = remAfterDays % 60
    val parts = buildList {
        if (days > 0) add("${days}d")
        if (hrs > 0) add("${hrs}h")
        if (mins > 0 && days == 0) add("${mins}m")
    }
    return parts.take(2).joinToString(" ").ifEmpty { "0m" }
}
