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

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

    @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(
                "operation" to "write",
                "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 operation`(): 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",
            ),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertEquals(
            "Missing 'operation' parameter. Expected 'write' or 'delete'.",
            (result as ValidationResult.Invalid).reason,
        )
    }

    @Test
    fun `validateRequest returns Invalid for missing path`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "write",
                "content" to "hello",
            ),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertEquals("Missing 'path' parameter. Expected String.", (result as ValidationResult.Invalid).reason)
    }

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

    @Test
    fun `validateRequest returns Invalid for unknown operation`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "unknown",
                "path" to tempDir.resolve("file.txt").toString(),
            ),
        )
        val result = tool.validateRequest(request)
        assertTrue(result is ValidationResult.Invalid)
        assertEquals("Unknown operation: unknown", (result as ValidationResult.Invalid).reason)
    }

    @Test
    fun `execute returns Success for write operation`(): 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(
                "operation" to "write",
                "path" to filePath.toString(),
                "content" to content,
            ),
        )

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        val success = result as ToolResult.Success
        // Output starts with the status line; a unified diff of the change is appended (F-019).
        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 Success for delete operation`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val filePath = tempDir.resolve("to_delete.txt")
        Files.writeString(filePath, "content to delete")

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

        val result = tool.execute(request)
        assertTrue(result is ToolResult.Success)
        val success = result as ToolResult.Success
        assertTrue(success.output.startsWith("File deleted successfully: $filePath"))
        assertEquals(invocationId, success.invocationId)
        assertFalse(Files.exists(filePath))
    }

    @Test
    fun `execute returns Failure for deleting non-existent file`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val filePath = tempDir.resolve("non_existent.txt")

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

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertEquals("File not found: $filePath", failure.reason)
        assertFalse(failure.recoverable)
    }

    @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 filePath = otherDir.resolve("illegal.txt")

        val request = createRequest(
            mapOf(
                "operation" to "write",
                "path" to filePath.toString(),
                "content" to "illegal",
            ),
        )

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Failure)
        val failure = result as ToolResult.Failure
        assertTrue(failure.reason.contains("is not in the allowed list"))
    }

    @Test
    fun `validateRequest returns Valid for existing file in allowed directory`(): Unit = runBlocking {
        val tempDir = Files.createTempDirectory("file_write_test_existing")
        val filePath = tempDir.resolve("existing.txt")
        Files.writeString(filePath, "content")
        val tool = FileWriteTool(allowedPaths = setOf(tempDir))
        val request = createRequest(
            mapOf(
                "operation" to "delete",
                "path" to filePath.toString(),
            ),
        )
        assertEquals(ValidationResult.Valid, tool.validateRequest(request))
    }

    @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("operation" to "write", "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 `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("operation" to "write", "path" to target.toString(), "content" to "new"),
        )

        val result = tool.execute(request)

        assertTrue(result is ToolResult.Success)
        assertEquals("new", Files.readString(target))
        // the staged temp file must not survive the swap
        val leftovers = Files.list(tempDir).use { stream ->
            stream.filter { it.fileName.toString().endsWith(".tmp") }.count()
        }
        assertEquals(0L, leftovers)
    }
}
