package com.correx.core.tools

import com.correx.core.approvals.Tier
import com.correx.core.events.events.AssessedIssue
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolCallAssessedEvent
import com.correx.core.events.events.ToolCallObservation
import com.correx.core.events.events.ToolInvocationRequestedEvent
import com.correx.core.events.events.ToolRequest
import com.correx.core.events.risk.RiskAction
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.ToolState
import kotlinx.datetime.Clock
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull

class ToolCallAssessmentReducerTest {

    private val reducer = DefaultToolReducer()
    private val inv = ToolInvocationId("inv-1")
    private val session = SessionId("s-1")
    private val stage = StageId("st-1")

    private fun stored(payload: com.correx.core.events.events.EventPayload, seq: Long) = StoredEvent(
        metadata = EventMetadata(
            eventId = EventId("e-$seq"),
            sessionId = session,
            timestamp = Clock.System.now(),
            schemaVersion = 1,
            causationId = null,
            correlationId = null,
        ),
        sequence = seq,
        sessionSequence = seq,
        payload = payload,
    )

    private val requested = ToolInvocationRequestedEvent(
        invocationId = inv,
        sessionId = session,
        stageId = stage,
        toolName = "file_write",
        tier = Tier.T1,
        request = ToolRequest(inv, session, stage, "file_write", mapOf("path" to "/etc/passwd")),
    )

    private val assessed = ToolCallAssessedEvent(
        invocationId = inv,
        sessionId = session,
        stageId = stage,
        toolName = "file_write",
        issues = listOf(AssessedIssue("PRIVILEGED_LOCATION", "privileged: /etc/passwd", "ERROR")),
        observations = listOf(ToolCallObservation("PATH_CONTAINMENT", mapOf("privileged" to "true"))),
        disposition = RiskAction.BLOCK,
        timestampMs = 1L,
    )

    @Test
    fun `assessment is reduced onto the matching invocation record`() {
        var state = ToolState()
        state = reducer.reduce(state, stored(requested, 0))
        state = reducer.reduce(state, stored(assessed, 1))

        val record = state.invocations.single()
        val assessment = assertNotNull(record.assessment)
        assertEquals(RiskAction.BLOCK, assessment.disposition)
        assertEquals("PRIVILEGED_LOCATION", assessment.issues.single().code)
        assertEquals("true", assessment.observations.single().facts["privileged"])
    }

    @Test
    fun `assessment for unknown invocation is a no-op`() {
        val state = reducer.reduce(ToolState(), stored(assessed, 0))
        assertEquals(0, state.invocations.size)
    }

    @Test
    fun `assessment record fields are correctly mapped from event`() {
        var state = ToolState()
        state = reducer.reduce(state, stored(requested, 0))
        state = reducer.reduce(state, stored(assessed, 1))

        val assessment = assertNotNull(state.invocations.single().assessment)
        assertEquals(1, assessment.issues.size)
        assertEquals(1, assessment.observations.size)
        assertEquals("PATH_CONTAINMENT", assessment.observations.single().ruleCode)
    }

    @Test
    fun `record without assessment has null assessment`() {
        var state = ToolState()
        state = reducer.reduce(state, stored(requested, 0))
        assertNull(state.invocations.single().assessment)
    }
}
