package com.correx.apps.server.health

import com.correx.core.events.events.HealthSubject
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.http.isSuccess

private const val DEFAULT_DEGRADE_FRACTION = 0.5
private const val DEFAULT_BASELINE_SAMPLES = 5
private const val LIVE = 1L
private const val DEAD = 0L

/** One liveness reading of the llama server: reachable, plus optional throughput telemetry. */
data class LivenessResult(val reachable: Boolean, val tokensPerSecond: Double? = null)

/** Pings the local inference process; implementations never throw (failures map to unreachable). */
interface LlamaLivenessClient {
    suspend fun ping(): LivenessResult
}

/**
 * Ktor-backed liveness client: a `GET $baseUrl/health` whose 2xx status is the only signal.
 * Tokens/sec telemetry is fed separately (the health endpoint reports reachability only), so this
 * client never populates [LivenessResult.tokensPerSecond]. Any transport exception is swallowed
 * into an unreachable result so the probe sees a clean signal.
 */
class HttpLlamaLivenessClient(
    private val httpClient: HttpClient,
    private val baseUrl: String,
) : LlamaLivenessClient {
    override suspend fun ping(): LivenessResult = runCatching {
        val response = httpClient.get("$baseUrl/health")
        LivenessResult(reachable = response.status.isSuccess())
    }.getOrElse { LivenessResult(reachable = false) }
}

/**
 * Watches the local inference process (observability-spec §4: "liveness + tokens/sec degradation").
 * Two failure modes: the server stops answering (DEGRADED on the `liveness` metric), or it stays
 * up but throughput collapses — KV-cache pressure or thermal throttle. The latter is judged against
 * a rolling PEAK of the last [baselineSamples] readings: when current tps falls below
 * [degradeFraction] of that peak, the subject degrades on the `tokens_per_sec` metric.
 *
 * The rolling window is per-run live state and never affects replay (Invariant #8): the monitor's
 * recorded edge events are the sole replay input, so a fresh process starting with an empty window
 * reconstructs identical history from the log.
 */
class LlamaServerHealthProbe(
    private val client: LlamaLivenessClient,
    private val degradeFraction: Double = DEFAULT_DEGRADE_FRACTION,
    private val baselineSamples: Int = DEFAULT_BASELINE_SAMPLES,
) : HealthProbe {

    override val subject: HealthSubject = HealthSubject.LLAMA_SERVER

    private val recent = ArrayDeque<Double>()

    override suspend fun observe(): HealthObservation {
        val result = runCatching { client.ping() }.getOrElse { LivenessResult(reachable = false) }

        if (!result.reachable) {
            return HealthObservation(
                subject = subject,
                status = HealthStatus.DEGRADED,
                metric = "liveness",
                observedValue = DEAD,
                threshold = LIVE,
                detail = "llama server unreachable",
            )
        }

        val tps = result.tokensPerSecond
            ?: return HealthObservation(
                subject = subject,
                status = HealthStatus.HEALTHY,
                metric = "liveness",
                observedValue = LIVE,
                threshold = LIVE,
                detail = "llama server reachable",
            )

        return throughputObservation(tps)
    }

    private fun throughputObservation(tps: Double): HealthObservation {
        recent.addLast(tps)
        while (recent.size > baselineSamples) recent.removeFirst()

        val baseline = recent.maxOrNull() ?: tps
        val floor = baseline * degradeFraction

        return if (baseline > 0.0 && tps < floor) {
            HealthObservation(
                subject = subject,
                status = HealthStatus.DEGRADED,
                metric = "tokens_per_sec",
                observedValue = tps.toLong(),
                threshold = floor.toLong(),
                detail = "throughput ${"%.1f".format(tps)} tok/s below ${"%.0f".format(degradeFraction * 100)}% " +
                    "of peak ${"%.1f".format(baseline)} tok/s",
            )
        } else {
            HealthObservation(
                subject = subject,
                status = HealthStatus.HEALTHY,
                metric = "tokens_per_sec",
                observedValue = tps.toLong(),
                threshold = floor.toLong(),
                detail = "throughput ${"%.1f".format(tps)} tok/s (peak ${"%.1f".format(baseline)} tok/s)",
            )
        }
    }
}
