package com.correx.core.toolintent

import com.correx.core.events.events.EgressHostsGrantedEvent
import com.correx.core.events.events.EventMetadata
import com.correx.core.events.events.EventPayload
import com.correx.core.events.events.InitialIntentEvent
import com.correx.core.events.events.StoredEvent
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.sessions.projections.EgressAllowlistProjection
import com.correx.core.toolintent.rules.NetworkHostRule
import com.correx.core.tools.contract.ToolCapability
import kotlinx.datetime.Instant
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

/**
 * Mirrors the build-site wiring in SessionOrchestrator.resolveSessionEgressHosts: a session's
 * EgressHostsGrantedEvents are folded through EgressAllowlistProjection, the resulting set is placed
 * on ToolCallAssessmentInput.sessionEgressHosts, and NetworkHostRule consults it. Uses an in-memory
 * list of StoredEvents as the fake store, matching EgressAllowlistProjectionTest's style.
 */
class SessionEgressResolutionTest {

    private val sessionId = SessionId("s1")

    private class FakeProbe : WorldProbe {
        override fun exists(path: Path) = true
        override fun resolveReal(path: Path): Path = path.toAbsolutePath().normalize()
    }

    private fun stored(payload: EventPayload, eventId: String, session: SessionId = sessionId) =
        StoredEvent(
            metadata = EventMetadata(
                eventId = EventId(eventId),
                sessionId = session,
                timestamp = Instant.parse("2026-01-01T00:00:00Z"),
                schemaVersion = 1,
                causationId = null,
                correlationId = null,
            ),
            sequence = 1L,
            sessionSequence = 1L,
            payload = payload,
        )

    // Replicates SessionOrchestrator.resolveSessionEgressHosts: fold the session log through the
    // projection. The store is faked as a plain List<StoredEvent>.
    private fun resolveSessionEgressHosts(events: List<StoredEvent>): Set<String> {
        val projection = EgressAllowlistProjection(sessionId)
        return events.fold(projection.initial()) { acc, e -> projection.apply(acc, e) }
    }

    private fun input(url: String, sessionEgressHosts: Set<String>) = ToolCallAssessmentInput(
        request = ToolRequest(
            ToolInvocationId("i"),
            sessionId,
            StageId("st"),
            "http_fetch",
            mapOf("url" to url),
        ),
        capabilities = setOf(ToolCapability.NETWORK_ACCESS),
        workspace = WorkspacePolicy(Path.of("/work")),
        probe = FakeProbe(),
        sessionEgressHosts = sessionEgressHosts,
    )

    @Test
    fun `granted egress host resolves into the assessment input and is allowed`() {
        val events = listOf(
            stored(InitialIntentEvent(sessionId, "do a thing"), "e1"),
            stored(EgressHostsGrantedEvent(sessionId, setOf("granted.com")), "e2"),
        )
        val resolved = resolveSessionEgressHosts(events)
        assertEquals(setOf("granted.com"), resolved)

        // Host is not in the static allow-list but is granted to the session → PROCEED.
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(input("https://granted.com/x", resolved))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `no grant resolves to empty and a non-static host prompts`() {
        val events = listOf(stored(InitialIntentEvent(sessionId, "do a thing"), "e1"))
        val resolved = resolveSessionEgressHosts(events)
        assertEquals(emptySet(), resolved)

        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(input("https://granted.com/x", resolved))
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals("NETWORK_HOST_NOT_ALLOWED", r.issues.single().code)
    }
}
