package com.correx.apps.server.metrics

import com.correx.core.events.events.InferenceCompletedEvent
import com.correx.core.events.events.ToolExecutionCompletedEvent
import com.correx.core.events.events.ToolExecutionFailedEvent
import com.correx.core.events.stores.EventStore
import kotlinx.serialization.Serializable

private const val PERCENT = 100.0
private const val REASON_BUCKET_MAX = 80
private const val TOP_REASONS = 5

@Serializable
data class ReasonCount(val reason: String, val count: Long)

@Serializable
data class ToolReliabilityRow(
    val toolName: String,
    val calls: Long,
    val failures: Long,
    val validityPct: Double,
)

@Serializable
data class ProviderToolReliability(
    val provider: String,
    val calls: Long,
    val failures: Long,
    val validityPct: Double,
    val byTool: List<ToolReliabilityRow>,
    val topFailureReasons: List<ReasonCount>,
)

@Serializable
data class ToolReliabilityReport(val providers: List<ProviderToolReliability>)

/**
 * Per-model tool-call reliability, derived by replaying the whole event log: for each provider, how
 * often did the tool calls it produced actually execute vs. fail (a failed `ToolExecutionFailedEvent`
 * is the deterministic signal of a malformed/invalid call — wrong args, missing required field, …).
 *
 * Tool events don't carry a providerId, so each tool outcome is attributed to the provider of the
 * most recent `InferenceCompletedEvent` in the same session (one model drives a session's inference
 * under the default routing). This is the measurement groundwork for capability-aware routing —
 * steering tool-heavy stages to models with a high observed validity rate.
 */
class ToolReliabilityInspectionService(private val eventStore: EventStore) {

    private class Agg {
        var calls: Long = 0
        var failures: Long = 0
        val reasons: MutableMap<String, Long> = linkedMapOf()
    }

    fun inspect(): ToolReliabilityReport {
        val sessionProvider = mutableMapOf<String, String>()
        val byProvider = linkedMapOf<String, Agg>()
        val byProviderTool = linkedMapOf<Pair<String, String>, Agg>()

        fun record(provider: String, tool: String, failureReason: String?) {
            val p = byProvider.getOrPut(provider) { Agg() }
            val pt = byProviderTool.getOrPut(provider to tool) { Agg() }
            p.calls++; pt.calls++
            if (failureReason != null) {
                p.failures++; pt.failures++
                val bucket = failureReason.lineSequence().firstOrNull().orEmpty().take(REASON_BUCKET_MAX)
                p.reasons[bucket] = (p.reasons[bucket] ?: 0) + 1
            }
        }

        eventStore.allEvents().forEach { stored ->
            when (val payload = stored.payload) {
                is InferenceCompletedEvent -> sessionProvider[payload.sessionId.value] = payload.providerId.value
                is ToolExecutionCompletedEvent ->
                    record(sessionProvider[payload.sessionId.value] ?: "unknown", payload.toolName, null)
                is ToolExecutionFailedEvent ->
                    record(sessionProvider[payload.sessionId.value] ?: "unknown", payload.toolName, payload.reason)
                else -> Unit
            }
        }

        val providers = byProvider.entries
            .sortedByDescending { it.value.calls }
            .map { (provider, agg) ->
                ProviderToolReliability(
                    provider = provider,
                    calls = agg.calls,
                    failures = agg.failures,
                    validityPct = validity(agg.calls, agg.failures),
                    byTool = byProviderTool.entries
                        .filter { it.key.first == provider }
                        .sortedByDescending { it.value.calls }
                        .map { (key, a) ->
                            ToolReliabilityRow(key.second, a.calls, a.failures, validity(a.calls, a.failures))
                        },
                    topFailureReasons = agg.reasons.entries
                        .sortedByDescending { it.value }
                        .take(TOP_REASONS)
                        .map { ReasonCount(it.key, it.value) },
                )
            }
        return ToolReliabilityReport(providers)
    }

    private fun validity(calls: Long, failures: Long): Double =
        if (calls == 0L) PERCENT else (calls - failures).toDouble() / calls * PERCENT
}
