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

class FileReadToolTest {

    private val sessionId = SessionId(UUID.randomUUID().toString())
    private val stageId = StageId(UUID.randomUUID().toString())
    private val invocationId = ToolInvocationId(UUID.randomUUID().toString())

    private fun createRequest(path: String): ToolRequest {
        return ToolRequest(
            invocationId = invocationId,
            sessionId = sessionId,
            stageId = stageId,
            toolName = "file_read",
            parameters = mapOf("path" to path),
        )
    }

    @Test
    fun `validateRequest returns Valid for allowed path`(): Unit = runBlocking {
        val tempFile = Files.createTempFile("test_allowed", ".txt")
        val tool = FileReadTool(allowedPaths = setOf(tempFile.parent))
        val request = createRequest(tempFile.toString())
        assertEquals(ValidationResult.Valid, tool.validateRequest(request))
    }

    @Test
    fun `validateRequest returns Invalid for disallowed path`(): Unit = runBlocking {
        val tempFile = Files.createTempFile("test_disallowed", ".txt")
        val tool = FileReadTool(allowedPaths = setOf(tempFile.parent.resolve("subdir")))
        val request = createRequest(tempFile.toString())
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertTrue((result as ValidationResult.Invalid).reason.contains("not in the allowed list"))
    }

    @Test
    fun `validateRequest returns Invalid for missing path parameter`(): Unit = runBlocking {
        val tool = FileReadTool()
        val request = ToolRequest(
            invocationId = invocationId,
            sessionId = sessionId,
            stageId = stageId,
            toolName = "file_read",
            parameters = emptyMap(),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertEquals(
            "Missing or invalid 'path' parameter. Expected String.",
            (result as ValidationResult.Invalid).reason,
        )
    }

    @Test
    fun `execute returns Success for valid file`(): Unit = runBlocking {
        val content = "Hello, world!"
        val tempFile = Files.createTempFile("test_content", ".txt")
        Files.writeString(tempFile, content)

        val tool = FileReadTool(allowedPaths = setOf(tempFile.parent))
        val request = createRequest(tempFile.toString())
        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        val success = result as ToolResult.Success
        assertEquals(content, success.output)
        assertEquals(invocationId, success.invocationId)
    }

    @Test
    fun `execute returns Failure for non-existent file`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("test_dir")
        val nonExistentFile = tempDir.resolve("missing.txt")

        val tool = FileReadTool(allowedPaths = setOf(nonExistentFile))
        val request = createRequest(nonExistentFile.toString())
        val result = tool.execute(request)

        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("File not found"))
        // A missing file is recoverable (F-009): the model often probes for files that may not
        // exist; the orchestrator feeds the miss back so it can adapt rather than failing the stage.
        assertTrue(failure.recoverable)
    }

    @Test
    fun `execute returns Failure for disallowed path`(): Unit = runBlocking {
        val tempFile = Files.createTempFile("test_disallowed_exec", ".txt")
        val tool = FileReadTool(allowedPaths = setOf(tempFile.parent.resolve("other")))
        val request = createRequest(tempFile.toString())
        val result = tool.execute(request)

        // It should fail at validation stage
        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("is not in the allowed list"))
    }

    @Test
    fun `execute lists directory entries sorted with sub-directory marker`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("test_listdir")
        Files.writeString(tempDir.resolve("README.md"), "x")
        Files.writeString(tempDir.resolve("build.gradle"), "x")
        Files.createDirectory(tempDir.resolve("src"))

        val tool = FileReadTool(allowedPaths = setOf(tempDir))
        val result = tool.execute(createRequest(tempDir.toString()))

        assertTrue(result is ToolResult.Success)
        // Sorted by name; directories suffixed with '/' so the model can navigate.
        assertEquals("README.md\nbuild.gradle\nsrc/", (result as ToolResult.Success).output)
    }

    @Test
    fun `outputCompressor strips blank lines and leading whitespace`() {
        val tool = FileReadTool()
        val raw = "  fun main() {\n\n      println()\n  }\n"
        val compressed = tool.outputCompressor.compress(raw, ToolOutputContext(exitCode = 0))
        assertEquals("fun main() {\nprintln()\n}", compressed)
    }
}
