package com.correx.core.tools

import com.correx.core.approvals.Tier
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolExecutionCompletedEvent
import com.correx.core.events.events.ToolExecutionFailedEvent
import com.correx.core.events.events.ToolExecutionRejectedEvent
import com.correx.core.events.events.ToolExecutionStartedEvent
import com.correx.core.events.events.ToolInvocationRequestedEvent
import com.correx.core.events.events.ToolInvokedEvent
import com.correx.core.events.events.ToolReceipt
import com.correx.core.events.events.ToolRequest
import com.correx.core.events.types.EventId
import com.correx.core.events.types.SessionId
import com.correx.core.events.types.StageId
import com.correx.core.events.types.ToolInvocationId
import com.correx.core.tools.state.ToolInvocationStatus
import com.correx.core.tools.state.ToolState
import kotlinx.datetime.Instant
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class DefaultToolReducerTest {

    private val reducer = DefaultToolReducer()
    private val timestamp = Instant.parse("2026-01-01T00:00:00Z")
    private val invocationId = ToolInvocationId("inv-1")
    private val sessionId = SessionId("s-1")
    private val stageId = StageId("st-1")

    private val request = ToolRequest(
        invocationId = invocationId,
        sessionId = sessionId,
        stageId = stageId,
        toolName = "echo"
    )

    private val receipt = ToolReceipt(
        invocationId = invocationId,
        toolName = "echo",
        exitCode = 0,
        outputSummary = "done",
        durationMs = 50L,
        tier = Tier.T0,
        timestamp = timestamp
    )

    private fun storedEvent(payload: com.correx.core.events.events.EventPayload): StoredEvent =
        StoredEvent(
            metadata = EventMetadata(
                eventId = EventId("e-1"),
                sessionId = sessionId,
                timestamp = timestamp,
                schemaVersion = 1,
                causationId = null,
                correlationId = null
            ),
            sequence = 1L,
            sessionSequence = 1L,
            payload = payload
        )

    @Test
    fun `ToolInvocationRequestedEvent appends REQUESTED record`() {
        val event = storedEvent(
            ToolInvocationRequestedEvent(invocationId, sessionId, stageId, "echo", Tier.T0, request)
        )
        val state = reducer.reduce(ToolState(), event)
        assertEquals(1, state.invocations.size)
        assertEquals(ToolInvocationStatus.REQUESTED, state.invocations[0].status)
        assertEquals(invocationId, state.invocations[0].invocationId)
    }

    @Test
    fun `ToolExecutionStartedEvent transitions record to STARTED`() {
        var state = reducer.reduce(
            ToolState(),
            storedEvent(ToolInvocationRequestedEvent(invocationId, sessionId, stageId, "echo", Tier.T0, request))
        )
        state = reducer.reduce(state, storedEvent(ToolExecutionStartedEvent(invocationId, sessionId, "echo")))
        assertEquals(ToolInvocationStatus.STARTED, state.invocations[0].status)
    }

    @Test
    fun `ToolExecutionCompletedEvent transitions to COMPLETED with receipt and completedAt`() {
        var state = reducer.reduce(
            ToolState(),
            storedEvent(ToolInvocationRequestedEvent(invocationId, sessionId, stageId, "echo", Tier.T0, request))
        )
        state = reducer.reduce(state, storedEvent(ToolExecutionStartedEvent(invocationId, sessionId, "echo")))
        state = reducer.reduce(
            state,
            storedEvent(ToolExecutionCompletedEvent(invocationId, sessionId, "echo", receipt))
        )
        val record = state.invocations[0]
        assertEquals(ToolInvocationStatus.COMPLETED, record.status)
        assertNotNull(record.receipt)
        assertEquals(0, record.receipt.exitCode)
        assertNotNull(record.completedAt)
    }

    @Test
    fun `ToolExecutionFailedEvent transitions to FAILED and sets completedAt`() {
        var state = reducer.reduce(
            ToolState(),
            storedEvent(ToolInvocationRequestedEvent(invocationId, sessionId, stageId, "echo", Tier.T0, request))
        )
        state = reducer.reduce(state, storedEvent(ToolExecutionFailedEvent(invocationId, sessionId, "echo", "timeout")))
        val record = state.invocations[0]
        assertEquals(ToolInvocationStatus.FAILED, record.status)
        assertNull(record.receipt)
        assertNotNull(record.completedAt)
    }

    @Test
    fun `ToolExecutionRejectedEvent transitions to REJECTED and sets completedAt`() {
        var state = reducer.reduce(
            ToolState(),
            storedEvent(ToolInvocationRequestedEvent(invocationId, sessionId, stageId, "echo", Tier.T0, request))
        )
        state = reducer.reduce(
            state,
            storedEvent(ToolExecutionRejectedEvent(invocationId, sessionId, "echo", Tier.T0, "tier denied"))
        )
        val record = state.invocations[0]
        assertEquals(ToolInvocationStatus.REJECTED, record.status)
        assertNotNull(record.completedAt)
    }

    @Test
    fun `unrelated event passes through unchanged`() {
        val state = ToolState()
        val result = reducer.reduce(state, storedEvent(ToolInvokedEvent("test-tool")))
        assertEquals(state, result)
    }
}
