package com.correx.infrastructure.workflow

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.nio.file.Files
import kotlin.io.path.writeText
import kotlin.test.assertEquals

class TomlWorkflowLoaderTest {

    private val loader = TomlWorkflowLoader()

    private val validToml = """
        id = "healthcheck"
        start = "collect"

        [[stages]]
        id = "collect"
        prompt = "prompts/collect.md"
        produces = [{ name = "system_stats", kind = "file_written" }]
        allowed_tools = ["ShellTool"]
        token_budget = 2048
        max_retries = 2

        [[stages]]
        id = "report"
        prompt = "prompts/report.md"
        needs = ["system_stats"]
        produces = [{ name = "report", kind = "file_written" }]
        token_budget = 2048

        [[transitions]]
        id = "collect-to-report"
        from = "collect"
        to = "report"
        condition_type = "artifact_present"
        condition_artifact_id = "system_stats"

        [[transitions]]
        id = "report-to-done"
        from = "report"
        to = "done"
        condition_type = "always_true"
    """.trimIndent()

    @Test
    fun `valid toml produces correct WorkflowGraph`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        val graph = loader.load(path)

        assertEquals("collect", graph.start.value)
        assertEquals(2, graph.stages.size)
        assertEquals(2, graph.transitions.size)

        val collectStage = graph.stages[graph.start]!!
        assertEquals(setOf("system_stats"), collectStage.produces.map { it.name.value }.toSet())
        assertEquals("prompts/collect.md", collectStage.metadata["prompt"])

        val reportStage = graph.stages.values.first { it.produces.any { a -> a.name.value == "report" } }
        assertEquals(setOf("system_stats"), reportStage.needs.map { it.value }.toSet())
    }

    @Test
    fun `missing stage reference throws WorkflowValidationException`() {
        val bad = validToml.replace("from = \"collect\"", "from = \"nonexistent\"")
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(bad) }
        assertThrows<WorkflowValidationException> { loader.load(path) }
    }

    @Test
    fun `duplicate transition ids throw WorkflowValidationException`() {
        val bad = validToml.replace("id = \"report-to-done\"", "id = \"collect-to-report\"")
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(bad) }
        assertThrows<WorkflowValidationException> { loader.load(path) }
    }

    @Test
    fun `unknown start stage throws WorkflowValidationException`() {
        val bad = validToml.replace("start = \"collect\"", "start = \"missing\"")
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(bad) }
        assertThrows<WorkflowValidationException> { loader.load(path) }
    }

    @Test
    fun `unsatisfied needs throws WorkflowValidationException`() {
        val bad = validToml.replace(
            "needs = [\"system_stats\"]",
            "needs = [\"nonexistent_artifact\"]",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(bad) }
        assertThrows<WorkflowValidationException> { loader.load(path) }
    }

    @Test
    fun `unknown artifact kind throws error`() {
        val bad = validToml.replace("kind = \"file_written\"", "kind = \"unknown_kind\"")
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(bad) }
        assertThrows<IllegalStateException> { loader.load(path) }
    }

    @Test
    fun `inject_artifact_kinds true maps to metadata key`() {
        val toml = validToml.replace(
            "prompt = \"prompts/collect.md\"",
            "prompt = \"prompts/collect.md\"\ninject_artifact_kinds = true",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(toml) }
        val graph = loader.load(path)
        val collectStage = graph.stages[graph.start]!!
        assertEquals("true", collectStage.metadata["injectArtifactKinds"])
    }

    @Test
    fun `inject_artifact_kinds absent means no metadata key`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        val graph = loader.load(path)
        val collectStage = graph.stages[graph.start]!!
        assertEquals(null, collectStage.metadata["injectArtifactKinds"])
    }

    @Test
    fun `description field is parsed and returned by loadMeta`() {
        val toml = validToml.replace(
            "start = \"collect\"",
            "start = \"collect\"\ndescription = \"Runs the health check pipeline\"",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(toml) }
        val meta = loader.loadMeta(path)
        assertEquals("Runs the health check pipeline", meta.description)
        assertEquals("healthcheck", meta.id)
    }

    @Test
    fun `description absent in loadMeta returns empty string`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        assertEquals("", loader.loadMeta(path).description)
    }

    @Test
    fun `writes maps to the stage write manifest`() {
        val toml = validToml.replace(
            "allowed_tools = [\"ShellTool\"]",
            "allowed_tools = [\"ShellTool\"]\nwrites = [\"core/**\", \"docs/*.md\"]",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(toml) }
        val graph = loader.load(path)
        val collectStage = graph.stages[graph.start]!!
        assertEquals(listOf("core/**", "docs/*.md"), collectStage.writeManifest)
    }

    @Test
    fun `writes absent means an empty manifest`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        val graph = loader.load(path)
        val collectStage = graph.stages[graph.start]!!
        assertEquals(emptyList(), collectStage.writeManifest)
    }

    @Test
    fun `static_analysis maps to the stage static-analysis commands`() {
        val toml = validToml.replace(
            "allowed_tools = [\"ShellTool\"]",
            "allowed_tools = [\"ShellTool\"]\nstatic_analysis = [\"./gradlew compileKotlin\", \"./gradlew detekt\"]",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(toml) }
        val graph = loader.load(path)
        val collectStage = graph.stages[graph.start]!!
        assertEquals(listOf("./gradlew compileKotlin", "./gradlew detekt"), collectStage.staticAnalysis)
    }

    @Test
    fun `static_analysis absent means no commands`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        val graph = loader.load(path)
        assertEquals(emptyList(), graph.stages[graph.start]!!.staticAnalysis)
    }

    @Test
    fun `ground_references true maps to metadata key`() {
        val toml = validToml.replace(
            "prompt = \"prompts/collect.md\"",
            "prompt = \"prompts/collect.md\"\nground_references = true",
        )
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(toml) }
        val graph = loader.load(path)
        assertEquals("true", graph.stages[graph.start]!!.metadata["groundReferences"])
    }

    @Test
    fun `ground_references absent means no metadata key`() {
        val path = Files.createTempFile("workflow", ".toml").also { it.writeText(validToml) }
        val graph = loader.load(path)
        assertEquals(null, graph.stages[graph.start]!!.metadata["groundReferences"])
    }
}
