package com.correx.infrastructure.tools.filesystem

import com.correx.core.events.events.ToolRequest
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.tools.contract.ToolResult
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.nio.file.Files
import java.util.*

class ListDirToolTest {

    private fun request(path: String? = null, recursive: Boolean? = null) = ToolRequest(
        invocationId = ToolInvocationId(UUID.randomUUID().toString()),
        sessionId = SessionId(UUID.randomUUID().toString()),
        stageId = StageId(UUID.randomUUID().toString()),
        toolName = "list_dir",
        parameters = buildMap {
            path?.let { put("path", it) }
            recursive?.let { put("recursive", it) }
        },
    )

    @Test
    fun `recursive listing prunes gitignored dirs and files`(): Unit = runBlocking {
        val root = Files.createTempDirectory("listdir")
        Files.writeString(root.resolve(".gitignore"), "node_modules\ndist\n*.log\n")
        Files.createDirectories(root.resolve("node_modules/pkg")).also { Files.writeString(it.resolve("x.js"), "x") }
        Files.createDirectories(root.resolve("dist")).also { Files.writeString(it.resolve("bundle.js"), "b") }
        Files.createDirectories(root.resolve("src")).also { Files.writeString(it.resolve("main.ts"), "m") }
        Files.writeString(root.resolve("debug.log"), "noise")
        Files.writeString(root.resolve("package.json"), "{}")

        val tool = ListDirTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request(recursive = true)) as ToolResult.Success).output

        assertTrue(out.contains("src/main.ts"), out)
        assertTrue(out.contains("package.json"), out)
        assertFalse(out.contains("node_modules"), out)   // whole subtree pruned, never descended
        assertFalse(out.contains("dist"), out)
        assertFalse(out.contains("debug.log"), out)
    }

    @Test
    fun `default is shallow — a bare list_dir does not descend`(): Unit = runBlocking {
        // Regression: a recursive default buried top-level answers ("does frontend/ exist?") under a
        // 400-entry alphabetical flood and drove models to re-issue the same list_dir 2-3x. A bare
        // call (no recursive param) must not descend a directory that already has a meaningful choice.
        val root = Files.createTempDirectory("listdir")
        Files.createDirectories(root.resolve("src/deep")).also { Files.writeString(it.resolve("f.ts"), "f") }
        Files.writeString(root.resolve("README.md"), "root file")
        val tool = ListDirTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request()) as ToolResult.Success).output
        assertTrue(out.contains("src/"), out)
        assertFalse(out.contains("deep"), out)
    }

    @Test
    fun `listing a file gives a recoverable file-vs-dir hint, not a bare error`(): Unit = runBlocking {
        // Regression (session a60c54b0): list_dir on a plain file returned a bare "Not a directory"
        // and models re-issued the identical call until cancel. The failure must name the remedy.
        val root = Files.createTempDirectory("listdir")
        Files.writeString(root.resolve("pages"), "i am a file")
        val tool = ListDirTool(allowedPaths = setOf(root), workingDir = root)
        val result = tool.execute(request(path = "pages")) as ToolResult.Failure
        assertTrue(result.recoverable, "file-vs-dir mismatch is recoverable")
        assertTrue(result.reason.contains("FILE"), result.reason)
        assertTrue(result.reason.contains("file_read"), result.reason)
    }

    @Test
    fun `non-recursive lists only immediate children`(): Unit = runBlocking {
        val root = Files.createTempDirectory("listdir")
        Files.createDirectories(root.resolve("src/deep")).also { Files.writeString(it.resolve("f.ts"), "f") }
        Files.writeString(root.resolve("README.md"), "root file")
        val tool = ListDirTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request(recursive = false)) as ToolResult.Success).output
        assertTrue(out.contains("src/"), out)
        assertFalse(out.contains("deep"), out)
    }

    @Test
    fun `non-recursive listing auto-descends a single-child directory chain`(): Unit = runBlocking {
        val root = Files.createTempDirectory("listdir")
        Files.createDirectories(root.resolve("core/kernel/src/main"))
        Files.writeString(root.resolve("core/kernel/src/main/App.kt"), "class App")
        Files.createDirectories(root.resolve("core/kernel/src/main/resources"))

        val tool = ListDirTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request(recursive = false)) as ToolResult.Success).output

        assertTrue(out.contains("<path>${root.resolve("core/kernel/src/main")}</path>"), out)
        assertTrue(out.contains("App.kt"), out)
        assertTrue(out.contains("resources/"), out)
        assertTrue(out.contains("auto-descended core → kernel → src → main"), out)
    }
}
