package com.correx.infrastructure.tools.filesystem

import com.correx.core.approvals.Tier
import com.correx.core.events.events.ToolRequest
import com.correx.core.events.types.ToolInvocationId
import com.correx.core.tools.contract.FileAffectingTool
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.CancellationException
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

/**
 * Deletes a file. Split out of [FileWriteTool] so deletion is an explicitly-named capability a model
 * must choose deliberately — it can never happen by getting a write-mode parameter wrong. Carries the
 * [ToolCapability.FILE_WRITE] capability (file mutation) and the same path jail as the writer.
 */
class FileDeleteTool(
    allowedPaths: Set<Path> = emptySet(),
    private val workingDir: Path? = null,
) : Tool, FileAffectingTool, ToolExecutor {

    private val normalizedAllowedPaths: Set<Path> = allowedPaths.map { it.normalize().toAbsolutePath() }.toSet()

    override val name: String = "file_delete"
    override val description: String = "Delete the file at the specified relative path"
    override val parametersSchema: JsonObject = buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            putJsonObject("path") {
                put("type", "string")
                put("description", "Relative path of the file to delete")
            }
        }
        put("required", buildJsonArray { add(JsonPrimitive("path")) })
    }
    override val tier: Tier = Tier.T2
    override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_WRITE)
    override val paramRoles: Map<String, ParamRole> = mapOf("path" to ParamRole.PATH)

    override fun affectedPaths(request: ToolRequest): Set<Path> {
        val pathString = request.parameters["path"] as? String ?: return emptySet()
        return setOf(resolvePath(pathString))
    }

    private fun resolvePath(pathString: String): Path {
        val raw = Paths.get(pathString)
        return when {
            raw.isAbsolute -> raw.normalize()
            workingDir != null -> workingDir.resolve(raw).normalize()
            else -> raw.toAbsolutePath().normalize()
        }
    }

    override fun validateRequest(request: ToolRequest): ValidationResult {
        val pathString = request.parameters["path"] as? String
            ?: return ValidationResult.Invalid(
                """Missing 'path' parameter (string). Call file_delete with {"path": "<relative path>"}.""",
            )
        return runCatching {
            val path = resolvePath(pathString)
            when {
                normalizedAllowedPaths.isEmpty() -> ValidationResult.Invalid("No paths are allowed.")
                !PathJail.isContained(path, normalizedAllowedPaths) ->
                    ValidationResult.Invalid("Path '$pathString' is not in the allowed list.")

                else -> ValidationResult.Valid
            }
        }.getOrElse { e -> mapExceptionToValidationResult(e) }
    }

    private fun mapExceptionToValidationResult(e: Throwable): ValidationResult =
        when (e) {
            is InvalidPathException -> ValidationResult.Invalid("Invalid path format: ${e.message}")
            is IOException -> ValidationResult.Invalid("IO error: ${e.message}")
            is SecurityException -> ValidationResult.Invalid("Security error: ${e.message}")
            else -> ValidationResult.Invalid(e.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 = resolvePath(pathString)
        runCatching {
            if (Files.deleteIfExists(path)) {
                ToolResult.Success(
                    invocationId = request.invocationId,
                    output = "File deleted successfully: $pathString",
                )
            } else {
                ToolResult.Failure(
                    invocationId = request.invocationId,
                    reason = "File not found: $pathString",
                    recoverable = false,
                )
            }
        }.getOrElse { e -> handleExecutionException(e, request.invocationId, pathString) }
    }

    private fun handleExecutionException(
        e: Throwable,
        invocationId: ToolInvocationId,
        pathString: String,
    ): ToolResult = when (e) {
        is CancellationException -> throw e
        is IOException -> ToolResult.Failure(invocationId, "IO error: ${e.message}", recoverable = false)
        is SecurityException ->
            ToolResult.Failure(invocationId, "Access denied: $pathString, ${e.message}", recoverable = false)

        else -> ToolResult.Failure(invocationId, e.message ?: "Unknown error occurred", recoverable = false)
    }
}
