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.nio.file.Path
import java.util.*

class WorkspaceSearchToolsTest {

    private fun request(toolName: String, params: Map<String, Any>) = ToolRequest(
        invocationId = ToolInvocationId(UUID.randomUUID().toString()),
        sessionId = SessionId(UUID.randomUUID().toString()),
        stageId = StageId(UUID.randomUUID().toString()),
        toolName = toolName,
        parameters = params,
    )

    private fun fixture(): Path {
        val root = Files.createTempDirectory("search")
        Files.writeString(root.resolve(".gitignore"), "node_modules\ndist\n")
        Files.createDirectories(root.resolve("src/hooks"))
        Files.writeString(root.resolve("src/hooks/useAuth.ts"), "export const useAuth = () => {}\n")
        Files.writeString(root.resolve("src/main.ts"), "import { useAuth } from './hooks/useAuth'\n")
        Files.writeString(root.resolve("README.md"), "# web-ui-design-spec lives here\n")
        Files.createDirectories(root.resolve("node_modules/pkg"))
        Files.writeString(root.resolve("node_modules/pkg/useAuth.ts"), "junk useAuth\n")
        Files.createDirectories(root.resolve("dist"))
        Files.writeString(root.resolve("dist/bundle.js"), "useAuth\n")
        return root
    }

    @Test
    fun `glob finds matching paths and prunes gitignored trees`(): Unit = runBlocking {
        val root = fixture()
        val tool = GlobTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request("glob", mapOf("pattern" to "**/*.ts"))) as ToolResult.Success).output

        assertTrue(out.contains("src/hooks/useAuth.ts"), out)
        assertTrue(out.contains("src/main.ts"), out)
        assertFalse(out.contains("node_modules"), out) // gitignored subtree never matched
    }

    @Test
    fun `glob with a leading path segment scopes the search`(): Unit = runBlocking {
        val root = fixture()
        val tool = GlobTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request("glob", mapOf("pattern" to "src/**/use*.ts"))) as ToolResult.Success).output
        assertTrue(out.contains("src/hooks/useAuth.ts"), out)
        assertFalse(out.contains("main.ts"), out)
    }

    @Test
    fun `grep finds a literal across files, skipping gitignored ones`(): Unit = runBlocking {
        val root = fixture()
        val tool = GrepTool(allowedPaths = setOf(root), workingDir = root)
        val out = (tool.execute(request("grep", mapOf("pattern" to "web-ui-design-spec"))) as ToolResult.Success).output
        assertTrue(out.contains("README.md:1:"), out)
    }

    @Test
    fun `grep respects an optional file glob and prunes gitignore`(): Unit = runBlocking {
        val root = fixture()
        val tool = GrepTool(allowedPaths = setOf(root), workingDir = root)
        val req = request("grep", mapOf("pattern" to "useAuth", "glob" to "**/*.ts"))
        val out = (tool.execute(req) as ToolResult.Success).output
        assertTrue(out.contains("src/hooks/useAuth.ts:"), out)
        assertTrue(out.contains("src/main.ts:"), out)
        assertFalse(out.contains("node_modules"), out) // gitignored
        assertFalse(out.contains("bundle.js"), out)     // not matched by *.ts glob (and gitignored)
    }

    @Test
    fun `grep rejects an invalid regex recoverably`(): Unit = runBlocking {
        val root = fixture()
        val tool = GrepTool(allowedPaths = setOf(root), workingDir = root)
        val result = tool.execute(request("grep", mapOf("pattern" to "[unterminated")))
        assertTrue(result is ToolResult.Failure && result.recoverable, result.toString())
    }

    @Test
    fun `search outside the jail is rejected`(): Unit = runBlocking {
        val root = fixture()
        val tool = GlobTool(allowedPaths = setOf(root), workingDir = root)
        val result = tool.execute(request("glob", mapOf("pattern" to "*", "path" to "/etc")))
        assertTrue(result is ToolResult.Failure, result.toString())
    }
}
