package com.correx.core.toolintent

import com.correx.core.events.events.ToolRequest
import com.correx.core.events.risk.RiskAction
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.toolintent.rules.NetworkHostRule
import com.correx.core.tools.contract.ToolCapability
import java.nio.file.Path
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class NetworkHostRuleTest {

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

    private fun input(
        params: Map<String, Any>,
        capabilities: Set<ToolCapability> = setOf(ToolCapability.NETWORK_ACCESS),
        sessionEgressHosts: Set<String> = emptySet(),
    ) = ToolCallAssessmentInput(
        request = ToolRequest(
            ToolInvocationId("i"),
            SessionId("s"),
            StageId("st"),
            "http_fetch",
            params,
        ),
        capabilities = capabilities,
        workspace = WorkspacePolicy(Path.of("/work")),
        probe = FakeProbe(),
        sessionEgressHosts = sessionEgressHosts,
    )

    @Test
    fun `appliesTo true only for NETWORK_ACCESS`() {
        val rule = NetworkHostRule(emptySet(), emptySet())
        assertTrue(rule.appliesTo(setOf(ToolCapability.NETWORK_ACCESS)))
        assertFalse(rule.appliesTo(setOf(ToolCapability.FILE_READ)))
        assertFalse(rule.appliesTo(setOf(ToolCapability.SHELL_EXEC)))
        assertFalse(rule.appliesTo(emptySet()))
    }

    @Test
    fun `denied host yields BLOCK with NETWORK_HOST_DENIED`() {
        val rule = NetworkHostRule(emptySet(), setOf("evil.com"))
        val r = rule.assess(input(mapOf("url" to "https://evil.com/x")))
        assertEquals(RiskAction.BLOCK, r.disposition)
        assertEquals(1, r.issues.size)
        assertEquals("NETWORK_HOST_DENIED", r.issues.single().code)
    }

    @Test
    fun `host outside allow-list yields PROMPT_USER with NETWORK_HOST_NOT_ALLOWED`() {
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(input(mapOf("url" to "https://other.com")))
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals(1, r.issues.size)
        assertEquals("NETWORK_HOST_NOT_ALLOWED", r.issues.single().code)
    }

    @Test
    fun `allow-list suffix match proceeds`() {
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(input(mapOf("url" to "https://api.good.com/p")))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `no lists configured any URL proceeds`() {
        val rule = NetworkHostRule(emptySet(), emptySet())
        val r = rule.assess(input(mapOf("url" to "https://anywhere.example.com/path")))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `session-granted host outside static allow-list proceeds`() {
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(
            input(mapOf("url" to "https://granted.com/x"), sessionEgressHosts = setOf("granted.com")),
        )
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `host in neither static nor session allow-list yields PROMPT_USER`() {
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(
            input(mapOf("url" to "https://other.com"), sessionEgressHosts = setOf("granted.com")),
        )
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals("NETWORK_HOST_NOT_ALLOWED", r.issues.single().code)
    }

    @Test
    fun `session-granted host honours suffix subdomain match`() {
        val rule = NetworkHostRule(setOf("good.com"), emptySet())
        val r = rule.assess(
            input(mapOf("url" to "https://api.granted.com/p"), sessionEgressHosts = setOf("granted.com")),
        )
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `denied host wins over a matching session grant`() {
        val rule = NetworkHostRule(emptySet(), setOf("granted.com"))
        val r = rule.assess(
            input(mapOf("url" to "https://granted.com/x"), sessionEgressHosts = setOf("granted.com")),
        )
        assertEquals(RiskAction.BLOCK, r.disposition)
        assertEquals("NETWORK_HOST_DENIED", r.issues.single().code)
    }

    @Test
    fun `non-URL string param is ignored`() {
        val rule = NetworkHostRule(setOf("good.com"), setOf("evil.com"))
        val r = rule.assess(input(mapOf("text" to "just text")))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
        assertTrue(r.observations.isEmpty())
    }
}
