package com.correx.apps.server.health

import com.correx.core.events.events.HealthDegradedEvent
import com.correx.core.events.events.HealthRestoredEvent
import com.correx.core.events.events.HealthSubject
import com.correx.infrastructure.persistence.InMemoryEventStore
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class HealthMonitorTest {

    /** Probe that returns a scripted sequence of statuses, one per tick (last value repeats). */
    private class ScriptedProbe(
        override val subject: HealthSubject,
        private val statuses: List<HealthStatus>,
    ) : HealthProbe {
        private var index = 0
        override suspend fun observe(): HealthObservation {
            val status = statuses[index.coerceAtMost(statuses.size - 1)]
            index++
            return HealthObservation(subject, status, "total_bytes", index.toLong(), 5_000, "tick $index")
        }
    }

    private fun monitor(
        store: InMemoryEventStore,
        probe: HealthProbe,
        initial: Map<HealthSubject, HealthStatus> = emptyMap(),
    ) = HealthMonitor(
        eventStore = store,
        probes = listOf(probe),
        intervalMs = 1_000,
        initialStatuses = initial,
    )

    @Test
    fun `emits only on status transitions`(): Unit = runBlocking {
        val store = InMemoryEventStore()
        val probe = ScriptedProbe(
            HealthSubject.DISK,
            listOf(
                HealthStatus.HEALTHY,  // tick 1: no edge (default healthy)
                HealthStatus.DEGRADED, // tick 2: edge -> degraded
                HealthStatus.DEGRADED, // tick 3: no edge
                HealthStatus.HEALTHY,  // tick 4: edge -> restored
            ),
        )
        val m = monitor(store, probe)
        repeat(4) { m.tick() }

        val events = store.read(SYSTEM_SESSION).map { it.payload }
        assertEquals(2, events.size, "exactly two edges expected, got $events")
        assertTrue(events[0] is HealthDegradedEvent)
        assertTrue(events[1] is HealthRestoredEvent)
    }

    @Test
    fun `seeded degraded status suppresses duplicate degraded on restart`(): Unit = runBlocking {
        val store = InMemoryEventStore()
        val probe = ScriptedProbe(HealthSubject.DISK, listOf(HealthStatus.DEGRADED))
        // Monitor restarts with last-known status already DEGRADED (rebuilt from the log).
        val m = monitor(store, probe, initial = mapOf(HealthSubject.DISK to HealthStatus.DEGRADED))
        m.tick()
        m.tick()
        assertTrue(store.read(SYSTEM_SESSION).isEmpty(), "no edge — already degraded before restart")
    }

    @Test
    fun `degraded event carries probe metric and threshold`(): Unit = runBlocking {
        val store = InMemoryEventStore()
        val probe = ScriptedProbe(HealthSubject.DISK, listOf(HealthStatus.DEGRADED))
        monitor(store, probe).tick()

        val event = store.read(SYSTEM_SESSION).single().payload as HealthDegradedEvent
        assertEquals(HealthSubject.DISK, event.subject)
        assertEquals("total_bytes", event.metric)
        assertEquals(5_000, event.threshold)
    }

    @Test
    fun `a failing probe does not abort the tick or emit`(): Unit = runBlocking {
        val store = InMemoryEventStore()
        val throwing = object : HealthProbe {
            override val subject = HealthSubject.EVENT_STORE
            override suspend fun observe(): HealthObservation = error("probe boom")
        }
        val ok = ScriptedProbe(HealthSubject.DISK, listOf(HealthStatus.DEGRADED))
        HealthMonitor(store, probes = listOf(throwing, ok), intervalMs = 1_000).tick()

        val events = store.read(SYSTEM_SESSION).map { it.payload }
        assertEquals(1, events.size)
        assertTrue(events.single() is HealthDegradedEvent)
    }
}
