package com.correx.apps.server.health

import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.HealthDegradedEvent
import com.correx.core.events.events.HealthRestoredEvent
import com.correx.core.events.events.HealthSubject
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.types.EventId
import com.correx.core.utils.TypeId
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class HealthProjectionTest {

    private val t0 = Instant.parse("2026-06-13T00:00:00Z")
    private fun at(seconds: Long): Instant = t0.plus(kotlin.time.Duration.parse("${seconds}s"))

    private fun stored(payload: EventPayload, seq: Long, at: Instant): StoredEvent =
        StoredEvent(
            metadata = EventMetadata(
                eventId = EventId("e$seq"),
                sessionId = SYSTEM_SESSION,
                timestamp = at,
                schemaVersion = 1,
                causationId = null,
                correlationId = null,
            ),
            sequence = seq,
            sessionSequence = seq,
            payload = payload,
        )

    private fun degraded(metric: String, value: Long, threshold: Long) =
        HealthDegradedEvent(SYSTEM_SESSION, HealthSubject.DISK, metric, value, threshold, "d")

    private fun restored(metric: String, value: Long) =
        HealthRestoredEvent(SYSTEM_SESSION, HealthSubject.DISK, metric, value, "r")

    private fun fold(events: List<StoredEvent>): HealthState {
        val projection = HealthProjection()
        return events.fold(projection.initial()) { state, e -> projection.apply(state, e) }
    }

    @Test
    fun `empty log yields no subjects`() {
        assertTrue(fold(emptyList()).subjects.isEmpty())
    }

    @Test
    fun `degraded then restored leaves subject healthy with latest observation`() {
        val state = fold(
            listOf(
                stored(degraded("total_bytes", 6_000, 5_000), 1, at(0)),
                stored(restored("total_bytes", 1_000), 2, at(60)),
            ),
        )
        val disk = state.subjects.getValue(HealthSubject.DISK)
        assertEquals(HealthStatus.HEALTHY, disk.status)
        assertEquals(1_000, disk.observedValue)
        assertEquals(at(60), disk.since)
    }

    @Test
    fun `last edge wins`() {
        val state = fold(
            listOf(
                stored(restored("total_bytes", 1_000), 1, at(0)),
                stored(degraded("growth_bytes_per_min", 80, 50), 2, at(30)),
            ),
        )
        val disk = state.subjects.getValue(HealthSubject.DISK)
        assertEquals(HealthStatus.DEGRADED, disk.status)
        assertEquals("growth_bytes_per_min", disk.metric)
        assertEquals(at(30), disk.since)
    }

    @Test
    fun `unrelated events are ignored`() {
        val before = fold(listOf(stored(degraded("total_bytes", 6_000, 5_000), 1, at(0))))
        assertNull(before.subjects[HealthSubject.LLAMA_SERVER])
        assertEquals(1, before.subjects.size)
    }
}
