package com.correx.infrastructure.workflow

import com.correx.core.events.events.PlanGroundingVerdict
import com.correx.core.events.types.StageId
import com.correx.core.transitions.conditions.AlwaysTrue
import com.correx.core.transitions.graph.BuildExpectation
import com.correx.core.transitions.graph.StageConfig
import com.correx.core.transitions.graph.TransitionEdge
import com.correx.core.transitions.graph.WorkflowGraph
import com.correx.core.events.types.TransitionId
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

class PlanGrounderTest {

    private fun graph(vararg stages: Pair<String, StageConfig>) = WorkflowGraph(
        id = "test",
        stages = stages.associate { StageId(it.first) to it.second },
        transitions = stages.toList().zipWithNext { (f, _), (t, _) ->
            TransitionEdge(TransitionId("$f-$t"), StageId(f), StageId(t), AlwaysTrue)
        }.toSet(),
        start = StageId(stages.first().first),
    )

    private val cmds = mapOf("build" to "npm run build", "typecheck" to "tsc --noEmit")

    @Test
    fun `stage building against a missing prerequisite is returned to architect`() {
        val g = graph("impl" to StageConfig(buildExpectation = BuildExpectation.PROJECT))
        val r = PlanGrounder.ground(g, repoMapPaths = setOf("src/main.ts"), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.RETURN_TO_ARCHITECT, r.verdict)
        assertTrue(r.findings.single().contains("impl"))
    }

    @Test
    fun `manifest present in repo map grounds the build`() {
        val g = graph("impl" to StageConfig(buildExpectation = BuildExpectation.PROJECT))
        val r = PlanGrounder.ground(g, repoMapPaths = setOf("package.json"), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }

    @Test
    fun `a plan stage that scaffolds the manifest grounds a later build`() {
        val g = graph(
            "scaffold" to StageConfig(writeManifest = listOf("package.json")),
            "impl" to StageConfig(buildExpectation = BuildExpectation.PROJECT),
        )
        val r = PlanGrounder.ground(g, repoMapPaths = emptySet(), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }

    @Test
    fun `no configured command degrades to a safe pass`() {
        val g = graph("impl" to StageConfig(buildExpectation = BuildExpectation.PROJECT))
        val r = PlanGrounder.ground(g, repoMapPaths = emptySet(), profileCommands = emptyMap())
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }

    @Test
    fun `a stage scoped to a path that neither exists nor is created is ungrounded`() {
        val g = graph("impl" to StageConfig(touches = listOf("frontend/**")))
        val r = PlanGrounder.ground(g, repoMapPaths = setOf("backend/main.kt"), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.RETURN_TO_ARCHITECT, r.verdict)
        assertTrue(r.findings.single().contains("frontend/**"))
    }

    @Test
    fun `a scope satisfied by an existing repo path grounds`() {
        val g = graph("impl" to StageConfig(touches = listOf("frontend/**")))
        val r = PlanGrounder.ground(g, repoMapPaths = setOf("frontend/App.tsx"), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }

    @Test
    fun `a scope satisfied by a file a plan stage creates grounds`() {
        val g = graph(
            "scaffold" to StageConfig(expectedFiles = listOf("frontend/index.html")),
            "impl" to StageConfig(touches = listOf("frontend/**")),
        )
        val r = PlanGrounder.ground(g, repoMapPaths = emptySet(), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }

    @Test
    fun `NONE expectation never grounds`() {
        val g = graph("impl" to StageConfig(buildExpectation = BuildExpectation.NONE))
        val r = PlanGrounder.ground(g, repoMapPaths = emptySet(), profileCommands = cmds)
        assertEquals(PlanGroundingVerdict.PASS, r.verdict)
    }
}
