package com.correx.apps.server.health

import com.correx.core.events.events.HealthSubject
import com.correx.core.events.stores.EventStore

/**
 * Health probe for the event store (observability-spec §4: EVENT_STORE subject).
 *
 * **Responsiveness** — every tick times a cheap [EventStore.lastGlobalSequence] read. The
 * suspend store call dispatches its own I/O, so the probe times just the read (no extra
 * dispatcher hop to inflate the metric). If the call throws (store unavailable) or takes
 * longer than [latencyWarnMs], the observation is DEGRADED; otherwise HEALTHY with the
 * measured latency as [HealthObservation.observedValue].
 *
 * **Why a read, not a write**: The event log is the single source of truth (Invariant #1).
 * Writing probe events on every health tick would pollute the journal and grow it
 * unbounded. A read gives an equally faithful heartbeat — if the store can't answer a
 * sequence query it can't be trusted to record facts. [HealthMonitor] owns edge detection
 * and records the observation as a [HealthDegradedEvent] / [HealthRestoredEvent]; this
 * probe only observes and returns (Invariants #8/#9).
 *
 * [nanoTimeSource] is injectable for deterministic testing (fake clocks, no real sleeps).
 */
class EventStoreHealthProbe(
    private val eventStore: EventStore,
    private val latencyWarnMs: Long = 500,
    private val nanoTimeSource: () -> Long = System::nanoTime,
) : HealthProbe {

    override val subject: HealthSubject = HealthSubject.EVENT_STORE

    override suspend fun observe(): HealthObservation {
        val startNs = nanoTimeSource()
        val result = runCatching { eventStore.lastGlobalSequence() }
        val elapsedMs = (nanoTimeSource() - startNs) / NS_PER_MS

        return when {
            result.isFailure -> degraded(
                metric = "read_latency_ms",
                value = elapsedMs,
                threshold = latencyWarnMs,
                detail = "lastGlobalSequence() threw: ${result.exceptionOrNull()?.message}",
            )
            elapsedMs >= latencyWarnMs -> degraded(
                metric = "read_latency_ms",
                value = elapsedMs,
                threshold = latencyWarnMs,
                detail = "lastGlobalSequence() took ${elapsedMs}ms (warn ≥ ${latencyWarnMs}ms)",
            )
            else -> healthy(
                metric = "read_latency_ms",
                value = elapsedMs,
                threshold = latencyWarnMs,
                detail = "event store responsive: lastGlobalSequence() in ${elapsedMs}ms",
            )
        }
    }

    private fun degraded(metric: String, value: Long, threshold: Long, detail: String) =
        HealthObservation(subject, HealthStatus.DEGRADED, metric, value, threshold, detail)

    private fun healthy(metric: String, value: Long, threshold: Long, detail: String) =
        HealthObservation(subject, HealthStatus.HEALTHY, metric, value, threshold, detail)

    companion object {
        private const val NS_PER_MS = 1_000_000L
    }
}
