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

    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>,
        toolName: String = "file_edit",
    ): ToolRequest {
        return ToolRequest(
            invocationId = invocationId,
            sessionId = sessionId,
            stageId = stageId,
            toolName = toolName,
            parameters = parameters,
        )
    }

    @Test
    fun `execute patch success`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_patch")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "line1\nline2\nline3\n")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))

        // Patch to replace line2 with line2_modified
        val patchContent = """
            --- a/test.txt
            +++ b/test.txt
            @@ -2 +2 @@
            -line2
            +line2_modified
        """.trimIndent() + "\n"

        val request = createRequest(
            mapOf(
                "operation" to "patch",
                "path" to filePath.toString(),
                "patch" to patchContent,
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success, "Expected Success but got $result")
        val content = Files.readString(filePath)
        assertEquals("line1\nline2_modified\nline3\n", content)
    }

    @Test
    fun `validateRequest returns Invalid for disallowed path`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_test")
        val otherDir = Files.createTempDirectory("other_dir")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "append",
                "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 non-existent file`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_test")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "append",
                "path" to tempDir.resolve("non_existent.txt").toString(),
                "content" to "hello",
            ),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertTrue((result as ValidationResult.Invalid).reason.contains("File not found"))
    }

    @Test
    fun `validateRequest rejects a replace whose target is absent or ambiguous, before execute`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_anchor")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "alpha\nbeta\nbeta\n")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))

        val absent = tool.validateRequest(
            createRequest(mapOf("operation" to "replace", "path" to filePath.toString(),
                "target" to "gamma", "replacement" to "x")),
        )
        assertTrue(absent is ValidationResult.Invalid)
        assertTrue((absent as ValidationResult.Invalid).reason.contains("Target not found"))

        val ambiguous = tool.validateRequest(
            createRequest(mapOf("operation" to "replace", "path" to filePath.toString(),
                "target" to "beta", "replacement" to "x")),
        )
        assertTrue(ambiguous is ValidationResult.Invalid)
        assertTrue((ambiguous as ValidationResult.Invalid).reason.contains("expected exactly once"))

        val unique = tool.validateRequest(
            createRequest(mapOf("operation" to "replace", "path" to filePath.toString(),
                "target" to "alpha", "replacement" to "x")),
        )
        assertEquals(ValidationResult.Valid, unique)
    }

    @Test
    fun `validateRequest returns Invalid for missing parameters`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_test")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "content")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))

        // Missing operation
        val req1 = createRequest(mapOf("path" to filePath.toString()))
        assertTrue(tool.validateRequest(req1) is ValidationResult.Invalid)

        // Missing content for append
        val req2 = createRequest(mapOf("operation" to "append", "path" to filePath.toString()))
        assertTrue(tool.validateRequest(req2) is ValidationResult.Invalid)

        // Missing target/replacement for replace
        val req3 = createRequest(mapOf("operation" to "replace", "path" to filePath.toString(), "target" to "content"))
        assertTrue(tool.validateRequest(req3) is ValidationResult.Invalid)

        // Missing patch for patch
        val req4 = createRequest(mapOf("operation" to "patch", "path" to filePath.toString()))
        assertTrue(tool.validateRequest(req4) is ValidationResult.Invalid)
    }

    @Test
    fun `execute append success`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_append")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "hello")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "append",
                "path" to filePath.toString(),
                "content" to " world",
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success)
        assertEquals("hello world", Files.readString(filePath))
    }

    @Test
    fun `execute replace success`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_replace")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "the quick brown fox")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                "target" to "brown",
                "replacement" to "red",
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success)
        assertEquals("the quick red fox", Files.readString(filePath))
    }

    @Test
    fun `replace tolerates indentation drift and rebases the replacement`(): Unit = runBlocking {
        // The model dropped the file's leading indentation in its target (the dominant Mode-2
        // failure). Flexible match must locate the block and re-indent the replacement to match.
        val tempDir = Files.createTempDirectory("file_edit_indent")
        val filePath = tempDir.resolve("Card.tsx")
        Files.writeString(
            filePath,
            "interface CardProps {\n  children: React.ReactNode;\n  className?: string;\n}\n",
        )
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                // No leading indentation, exactly as the model emitted it.
                "target" to "children: React.ReactNode;\nclassName?: string;",
                "replacement" to "children: React.ReactNode;\nclassName?: string;\ntitle?: string;",
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success, "indent-drift replace should succeed")
        // Every replaced line rebased to the file's real 2-space indent, including the new one.
        assertEquals(
            "interface CardProps {\n  children: React.ReactNode;\n  className?: string;\n  title?: string;\n}\n",
            Files.readString(filePath),
        )
    }

    @Test
    fun `replace accepts content as an alias for replacement`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_alias")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "the quick brown fox")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                "target" to "brown",
                "content" to "red", // model sent the append-style key on a replace
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success)
        assertEquals("the quick red fox", Files.readString(filePath))
    }

    @Test
    fun `execute replace failure zero matches`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_replace_fail")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "the quick brown fox")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                "target" to "green",
                "replacement" to "red",
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("not found"))
    }

    @Test
    fun `execute replace not-found suggests nearest line`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_replace_suggest")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "import React from 'react';\nconst greeting = 'hello world';\n")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                // whitespace/quote drift from the real line — the classic near-miss
                "target" to "const greeting = \"hello world\"",
                "replacement" to "const greeting = 'hi';",
            ),
        )

        val failure = tool.execute(request) as ToolResult.Failure
        assertTrue(failure.reason.contains("not found"))
        assertTrue(failure.reason.contains("Did you mean"))
        assertTrue(failure.reason.contains("const greeting = 'hello world';"))
    }

    @Test
    fun `execute replace failure multiple matches`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_replace_fail_multi")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "fox fox fox")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "replace",
                "path" to filePath.toString(),
                "target" to "fox",
                "replacement" to "dog",
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("found 3 times"))
    }


    @Test
    fun `execute patch failure`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_edit_patch_fail")
        val filePath = tempDir.resolve("test.txt")
        Files.writeString(filePath, "original")
        val tool = FileEditTool(allowedPaths = setOf(tempDir))

        val patchContent = """
            --- test.txt
            +++ test.txt
            @@ -1 +1 @@
            -nonexistent
            +something
        """.trimIndent()

        val request = createRequest(
            mapOf(
                "operation" to "patch",
                "path" to filePath.toString(),
                "patch" to patchContent,
            ),
        )

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("Patch failed"))
    }
}
