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.ExecInterpreterRule
import com.correx.core.tools.contract.ParamRole
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 ExecInterpreterRuleTest {

    private val rule = ExecInterpreterRule(setOf("bash", "python", "python3"))

    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.SHELL_EXEC),
    ) = ToolCallAssessmentInput(
        request = ToolRequest(
            ToolInvocationId("i"),
            SessionId("s"),
            StageId("st"),
            "shell_exec",
            params,
        ),
        capabilities = capabilities,
        workspace = WorkspacePolicy(Path.of("/work")),
        probe = FakeProbe(),
    )

    @Test
    fun `appliesTo true for SHELL_EXEC`() {
        assertTrue(rule.appliesTo(setOf(ToolCapability.SHELL_EXEC)))
    }

    @Test
    fun `appliesTo true for PROCESS_SPAWN`() {
        assertTrue(rule.appliesTo(setOf(ToolCapability.PROCESS_SPAWN)))
    }

    @Test
    fun `appliesTo false for FILE_READ`() {
        assertFalse(rule.appliesTo(setOf(ToolCapability.FILE_READ)))
    }

    @Test
    fun `appliesTo false for empty set`() {
        assertFalse(rule.appliesTo(emptySet()))
    }

    @Test
    fun `argv-style bash invocation yields PROMPT_USER and INTERPRETER_EXECUTION`() {
        val r = rule.assess(input(mapOf("argv" to """["bash","script.sh"]""")))
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals(1, r.issues.size)
        assertEquals("INTERPRETER_EXECUTION", r.issues.single().code)
    }

    @Test
    fun `argv-style non-interpreter ls proceeds with no issues`() {
        val r = rule.assess(input(mapOf("argv" to """["ls","-la"]""")))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `raw command line python yields PROMPT_USER`() {
        val r = rule.assess(input(mapOf("cmd" to "python foo.py")))
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals("INTERPRETER_EXECUTION", r.issues.single().code)
    }

    @Test
    fun `absolute path interpreter python3 yields PROMPT_USER via basename detection`() {
        val r = rule.assess(input(mapOf("cmd" to "/usr/bin/python3 x.py")))
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals("INTERPRETER_EXECUTION", r.issues.single().code)
    }

    @Test
    fun `declared EXEC_COMMAND role flags an argv list invoking python`() {
        val r = rule.assess(
            input(mapOf("argv" to listOf("python", "foo.py")))
                .copy(paramRoles = mapOf("argv" to ParamRole.EXEC_COMMAND)),
        )
        assertEquals(RiskAction.PROMPT_USER, r.disposition)
        assertEquals("INTERPRETER_EXECUTION", r.issues.single().code)
    }

    @Test
    fun `declared EXEC_COMMAND role ignores non-declared params`() {
        val r = rule.assess(
            input(mapOf("argv" to listOf("ls", "-la"), "note" to "python is great"))
                .copy(paramRoles = mapOf("argv" to ParamRole.EXEC_COMMAND)),
        )
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }
}
