package com.correx.infrastructure.tools.filesystem

import com.correx.core.approvals.Tier
import com.correx.core.events.events.ToolRequest
import com.correx.core.tools.compression.CompressionRule.StripBlankLines
import com.correx.core.tools.compression.CompressionRule.StripLeadingWhitespace
import com.correx.core.tools.compression.DeclarativeCompressor
import com.correx.core.tools.compression.OutputCompressionSpec
import com.correx.core.tools.compression.ToolOutputCompressor
import com.correx.core.tools.contract.ParamRole
import com.correx.core.tools.contract.Tool
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.tools.contract.ToolExecutor
import com.correx.core.tools.contract.ToolResult
import com.correx.core.tools.contract.ValidationResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import java.io.IOException
import java.nio.file.Files
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.nio.file.Paths

class FileReadTool(
    private val allowedPaths: Set<Path> = emptySet(),
) : Tool, ToolExecutor {

    override val name: String = "file_read"
    override val description: String = "Read content from a file at the specified path"
    override val parametersSchema: JsonObject = buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            putJsonObject("path") {
                put("type", "string")
                put("description", "Relative path to the file to read")
            }
            putJsonObject("start_line") {
                put("type", "integer")
                put("description", "First line to read, 1-indexed inclusive. Omit to read from beginning.")
            }
            putJsonObject("end_line") {
                put("type", "integer")
                put("description", "Last line to read, 1-indexed inclusive. Omit to read to end of file.")
            }
        }
        put("required", buildJsonArray { add(JsonPrimitive("path")) })
    }
    override val tier: Tier = Tier.T1
    override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_READ)
    override val paramRoles: Map<String, ParamRole> = mapOf("path" to ParamRole.PATH)
    override val outputCompressor: ToolOutputCompressor = DeclarativeCompressor(
        OutputCompressionSpec(listOf(StripBlankLines, StripLeadingWhitespace)),
    )

    override fun validateRequest(request: ToolRequest): ValidationResult {
        val pathString = request.parameters["path"] as? String
            ?: return ValidationResult.Invalid("Missing or invalid 'path' parameter. Expected String.")
        val startLine = request.parameters["start_line"]?.toString()?.toIntOrNull()
        val endLine = request.parameters["end_line"]?.toString()?.toIntOrNull()

        if (startLine != null && startLine < 1)
            return ValidationResult.Invalid("'start_line' must be >= 1.")
        if (endLine != null && endLine < 1)
            return ValidationResult.Invalid("'end_line' must be >= 1.")
        if (startLine != null && endLine != null && endLine < startLine)
            return ValidationResult.Invalid("'end_line' must be >= 'start_line'.")

        return runCatching {
            val path = Paths.get(pathString)
            if (!PathJail.isContained(path, allowedPaths))
                ValidationResult.Invalid("Path '$pathString' is not in the allowed list")
            else
                ValidationResult.Valid
        }.getOrElse {
            when (it) {
                is InvalidPathException -> ValidationResult.Invalid("Invalid path format: ${it.message}")
                is IOException -> ValidationResult.Invalid("IO error: ${it.message}")
                is SecurityException -> ValidationResult.Invalid("Security error: ${it.message}")
                else -> ValidationResult.Invalid(it.message ?: "Unknown error occurred")
            }
        }
    }

    override suspend fun execute(request: ToolRequest): ToolResult = withContext(Dispatchers.IO) {
        val validation = validateRequest(request)
        if (validation is ValidationResult.Invalid)
            return@withContext ToolResult.Failure(
                invocationId = request.invocationId,
                reason = validation.reason,
                recoverable = false,
            )

        val pathString = request.parameters["path"] as String
        val path = Paths.get(pathString).normalize().toAbsolutePath()
        val startLine = request.parameters["start_line"]?.toString()?.toIntOrNull()
        val endLine = request.parameters["end_line"]?.toString()?.toIntOrNull()

        if (!Files.exists(path))
            return@withContext ToolResult.Failure(
                invocationId = request.invocationId,
                reason = "File not found: $pathString",
                recoverable = false,
            )

        runCatching {
            val lines = Files.readAllLines(path)
            val start = ((startLine ?: 1) - 1).coerceAtLeast(0)
            val end = (endLine ?: lines.size).coerceAtMost(lines.size)
            val content = lines.subList(start, end).joinToString("\n")
            ToolResult.Success(
                invocationId = request.invocationId,
                output = content,
            )
        }.getOrElse {
            ToolResult.Failure(
                invocationId = request.invocationId,
                reason = "Failed to read file: ${it.message}",
                recoverable = false,
            )
        }
    }
}
