import com.correx.core.events.events.ChatSessionStartedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolInvokedEvent
import com.correx.core.events.serialization.eventJson
import com.correx.core.events.types.SessionId
import com.correx.core.sessions.projections.Projection
import com.correx.testing.fixtures.DeterministicHarness
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class DeterministicHarnessTest {

    private data class SimpleState(val eventCount: Int = 0)

    private val simpleProjection = object : Projection<SimpleState> {
        override fun initial() = SimpleState()
        override fun apply(state: SimpleState, event: StoredEvent) =
            state.copy(eventCount = state.eventCount + 1)
    }

    @Test
    fun `two harnesses with same payloads yield identical events`() = runBlocking {
        val harness1 = DeterministicHarness()
        val harness2 = DeterministicHarness()

        val sessionId = SessionId("test-session")
        val payload1 = ChatSessionStartedEvent(sessionId)
        val payload2 = ToolInvokedEvent("read_file")

        harness1.givenEvents(sessionId, payload1, payload2)
        harness2.givenEvents(sessionId, payload1, payload2)

        val events1 = harness1.eventStore.read(sessionId)
        val events2 = harness2.eventStore.read(sessionId)

        assertEquals(events1.size, events2.size)
        events1.zip(events2).forEach { (e1, e2) ->
            assertEquals(e1.metadata.eventId, e2.metadata.eventId, "eventIds must match")
            assertEquals(e1.metadata.timestamp, e2.metadata.timestamp, "timestamps must match")
            assertEquals(e1.sequence, e2.sequence, "sequences must match")
            assertEquals(e1.sessionSequence, e2.sessionSequence, "sessionSequences must match")
            assertEquals(
                eventJson.encodeToString(StoredEvent.serializer(), e1),
                eventJson.encodeToString(StoredEvent.serializer(), e2),
                "stored event JSON must be byte-identical",
            )
        }
    }

    @Test
    fun `rebuild applies events deterministically`() = runBlocking {
        val harness = DeterministicHarness()
        val sessionId = SessionId("rebuild-test")

        harness.givenEvents(
            sessionId,
            ChatSessionStartedEvent(sessionId),
            ToolInvokedEvent("read_file"),
            ToolInvokedEvent("write_file")
        )

        val state = harness.rebuild(sessionId, simpleProjection)
        assertEquals(3, state.eventCount)
    }

    @Test
    fun `clock is fixed for deterministic timestamps`(): Unit = runBlocking {
        val harness = DeterministicHarness()
        val sessionId = SessionId("clock-test")

        harness.givenEvents(sessionId, ChatSessionStartedEvent(sessionId))

        val event = harness.eventStore.read(sessionId).first()
        assertEquals("2026-01-01T00:00:00Z", event.metadata.timestamp.toString())
    }
}
