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.ReadBeforeWriteRule
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 ReadBeforeWriteRuleTest {

    private val workspace = Path.of("/work/project")
    private val rule = ReadBeforeWriteRule()

    /** A probe where existence is configurable; resolveReal just normalizes (no symlinks). */
    private class FakeProbe(private val existing: Set<Path> = emptySet()) : WorldProbe {
        override fun exists(path: Path): Boolean = path.toAbsolutePath().normalize() in existing
        override fun resolveReal(path: Path): Path = path.toAbsolutePath().normalize()
    }

    private fun abs(p: String): Path = Path.of(p).toAbsolutePath().normalize()

    private fun input(
        pathArg: String,
        probe: WorldProbe,
        read: Set<String> = emptySet(),
        tool: String = "file_edit",
    ) = ToolCallAssessmentInput(
        request = ToolRequest(ToolInvocationId("i"), SessionId("s"), StageId("st"), tool, mapOf("path" to pathArg)),
        capabilities = setOf(ToolCapability.FILE_WRITE),
        workspace = WorkspacePolicy(workspace, emptyList()),
        probe = probe,
        session = SessionContext(reads = read),
    )

    @Test
    fun `applies only to FILE_WRITE`() {
        assertTrue(rule.appliesTo(setOf(ToolCapability.FILE_WRITE)))
        assertFalse(rule.appliesTo(setOf(ToolCapability.FILE_READ)))
        assertFalse(rule.appliesTo(emptySet()))
    }

    @Test
    fun `editing an existing file that was not read is blocked`() {
        val target = "/work/project/src/A.kt"
        val r = rule.assess(input(target, FakeProbe(existing = setOf(abs(target)))))
        assertEquals(RiskAction.BLOCK, r.disposition)
        assertEquals("READ_BEFORE_WRITE", r.issues.single().code)
        assertEquals("false", r.observations.single().facts["read"])
    }

    @Test
    fun `editing an existing file that was read proceeds`() {
        val target = "/work/project/src/A.kt"
        val r = rule.assess(input(target, FakeProbe(existing = setOf(abs(target))), read = setOf(target)))
        assertEquals(RiskAction.PROCEED, r.disposition)
        assertTrue(r.issues.isEmpty())
    }

    @Test
    fun `creating a brand-new file proceeds without a prior read`() {
        val target = "/work/project/src/New.kt" // not in existing → new file
        val r = rule.assess(input(target, FakeProbe(existing = emptySet()), tool = "file_write"))
        assertEquals(RiskAction.PROCEED, r.disposition)
    }

    @Test
    fun `a relative read of the same file satisfies an absolute write`() {
        val target = "/work/project/src/A.kt"
        // read recorded as a workspace-relative path; resolves to the same real path as the write.
        val r = rule.assess(input(target, FakeProbe(existing = setOf(abs(target))), read = setOf("src/A.kt")))
        assertEquals(RiskAction.PROCEED, r.disposition)
    }
}
