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 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 FileWriteToolTest {

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

    private fun createRequest(parameters: Map<String, String>): ToolRequest = ToolRequest(
        invocationId = invocationId,
        sessionId = sessionId,
        stageId = stageId,
        toolName = "file_write",
        parameters = parameters,
    )

    @Test
    fun `validateRequest returns Valid for allowed path with content`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf("path" to tempDir.resolve("file.txt").toString(), "content" to "hello"),
        )
        assertEquals(ValidationResult.Valid, tool.validateRequest(request))
    }

    @Test
    fun `execute fails recoverably when path is a directory`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val subDir = Files.createDirectory(tempDir.resolve("views"))
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val result = tool.execute(createRequest(mapOf("path" to subDir.toString(), "content" to "x")))
        assertTrue(result is ToolResult.Failure)
        result as ToolResult.Failure
        // Recoverable so the stage's tool loop feeds it back instead of dead-ending the workflow.
        assertTrue(result.recoverable)
        assertTrue(result.reason.contains("is a directory"))
    }

    @Test
    fun `validateRequest returns Invalid for disallowed path`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val otherDir = Files.createTempDirectory("other_dir")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf("path" to otherDir.resolve("file.txt").toString(), "content" to "hello"),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertTrue((result as ValidationResult.Invalid).reason.contains("is not in the allowed list"))
    }

    @Test
    fun `validateRequest returns Invalid for missing path with actionable message`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val result = tool.validateRequest(createRequest(mapOf("content" to "hello")))
        assertTrue(result is ValidationResult.Invalid)
        val reason = (result as ValidationResult.Invalid).reason
        assertTrue(reason.contains("Missing 'path'"))
        // actionable: shows the required call shape
        assertTrue(reason.contains("\"content\""))
    }

    @Test
    fun `validateRequest returns Invalid for missing content with actionable message`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(mapOf("path" to tempDir.resolve("file.txt").toString()))
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        val reason = (result as ValidationResult.Invalid).reason
        assertTrue(reason.contains("Missing 'content'"))
        assertTrue(reason.contains("file_write"))
    }

    @Test
    fun `execute writes content and attaches a diff`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val filePath = tempDir.resolve("test.txt")
        val content = "Hello, FileWriteTool!"
        val request = createRequest(mapOf("path" to filePath.toString(), "content" to content))

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        val success = result as ToolResult.Success
        assertTrue(success.output.startsWith("File written successfully to $filePath"))
        assertTrue(success.metadata.containsKey("diff"))
        assertEquals(invocationId, success.invocationId)
        assertEquals(content, Files.readString(filePath))
    }

    @Test
    fun `execute returns Failure for disallowed path`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val otherDir = Files.createTempDirectory("other_dir")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf("path" to otherDir.resolve("illegal.txt").toString(), "content" to "illegal"),
        )
        val result = tool.execute(request)
        assertTrue(result is ToolResult.Failure)
        assertTrue((result as ToolResult.Failure).reason.contains("is not in the allowed list"))
    }

    @Test
    fun `write creates missing parent directories and writes content`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_atomic")
        val target = tempDir.resolve("nested/deep/file.txt")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(mapOf("path" to target.toString(), "content" to "hello"))

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        assertTrue(Files.exists(target))
        assertEquals("hello", Files.readString(target))
    }

    @Test
    fun `clobber guard blocks overwriting real code with an elided stub`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_clobber")
        val target = tempDir.resolve("Routes.kt")
        Files.writeString(target, (1..273).joinToString("\n") { "line $it = something()" })
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val stub = "fun routes() {\n    // ... existing routes ...\n    ...\n}"
        val result = tool.execute(createRequest(mapOf("path" to target.toString(), "content" to stub)))

        assertTrue(result is ToolResult.Failure)
        result as ToolResult.Failure
        assertTrue(result.recoverable)
        assertTrue(result.reason.contains("file_edit"))
        assertEquals(273, Files.readString(target).lines().size) // original untouched
    }

    @Test
    fun `clobber guard allows a genuine shrink with no placeholders`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_shrink")
        val target = tempDir.resolve("Routes.kt")
        Files.writeString(target, (1..273).joinToString("\n") { "line $it = something()" })
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val trimmed = (1..40).joinToString("\n") { "kept $it = something()" }
        val result = tool.execute(createRequest(mapOf("path" to target.toString(), "content" to trimmed)))

        assertTrue(result is ToolResult.Success)
        assertEquals(trimmed, Files.readString(target))
    }

    @Test
    fun `write atomically overwrites an existing file and leaves no temp files`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_overwrite")
        val target = tempDir.resolve("file.txt")
        Files.writeString(target, "old")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(mapOf("path" to target.toString(), "content" to "new"))

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        assertEquals("new", Files.readString(target))
        val leftovers = Files.list(tempDir).use { stream ->
            stream.filter { it.fileName.toString().endsWith(".tmp") }.count()
        }
        assertEquals(0L, leftovers)
    }
}
