package com.correx.core.toolintent

import com.correx.core.approvals.Tier
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.ToolExecutionCompletedEvent
import com.correx.core.events.events.ToolInvocationRequestedEvent
import com.correx.core.events.events.SessionWorkingTaskEvent
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.TaskId
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.contract.ToolCapability
import kotlinx.datetime.Instant
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class SessionContextProjectionTest {

    private val session = SessionId("s")
    private val projection = SessionContextProjection(session)

    private var seq = 0L
    private fun stored(payload: EventPayload) = StoredEvent(
        metadata = EventMetadata(EventId("e-$seq"), session, Instant.parse("2026-01-01T00:00:00Z"), 1, null, null),
        sequence = ++seq,
        sessionSequence = seq,
        payload = payload,
    )

    private fun readRequested(invId: String, path: String) = stored(
        ToolInvocationRequestedEvent(
            invocationId = ToolInvocationId(invId),
            sessionId = session,
            stageId = StageId("st"),
            toolName = "file_read",
            tier = Tier.T1,
            request = ToolRequest(ToolInvocationId(invId), session, StageId("st"), "file_read", mapOf("path" to path)),
            capabilities = setOf(ToolCapability.FILE_READ),
        ),
    )

    private fun completed(
        invId: String,
        affected: List<String> = emptyList(),
        structured: Map<String, Any> = emptyMap(),
    ) = stored(
        ToolExecutionCompletedEvent(
            invocationId = ToolInvocationId(invId),
            sessionId = session,
            toolName = "tool",
            receipt = ToolReceipt(
                invocationId = ToolInvocationId(invId),
                toolName = "tool",
                exitCode = 0,
                outputSummary = "ok",
                structuredOutput = structured,
                affectedEntities = affected,
                durationMs = 1,
                tier = Tier.T1,
                timestamp = Instant.parse("2026-01-01T00:00:00Z"),
            ),
        ),
    )

    private fun fold(events: List<StoredEvent>) =
        events.fold(projection.initial()) { acc, e -> projection.apply(acc, e) }

    @Test
    fun `a completed read enters reads but a pending one does not`() {
        assertEquals(setOf("src/A.kt"), fold(listOf(readRequested("r1", "src/A.kt"), completed("r1"))).reads)
        assertTrue(fold(listOf(readRequested("r1", "src/A.kt"))).reads.isEmpty())
    }

    @Test
    fun `writes come from the completion's affectedEntities`() {
        val ctx = fold(listOf(completed("w1", affected = listOf("core/A.kt", "core/B.kt"))))
        assertEquals(setOf("core/A.kt", "core/B.kt"), ctx.writes)
        assertTrue(ctx.reads.isEmpty())
    }

    @Test
    fun `a full read records its content hash for the read path`() {
        val ctx = fold(
            listOf(readRequested("r1", "src/A.kt"), completed("r1", structured = mapOf("contentHash" to "H1"))),
        )
        assertEquals(mapOf("src/A.kt" to "H1"), ctx.readHashes)
    }

    @Test
    fun `a SessionWorkingTask event sets the active task and the latest wins`() {
        val ctx = fold(
            listOf(
                stored(SessionWorkingTaskEvent(session, TaskId("auth-1"), listOf("core/a/**"))),
                stored(SessionWorkingTaskEvent(session, TaskId("auth-2"), listOf("core/auth/**"))),
            ),
        )
        assertEquals("auth-2", ctx.activeTask?.taskId)
        assertEquals(listOf("core/auth/**"), ctx.activeTask?.scope)
    }
}
