package com.correx.infrastructure.tools.mcp

import com.correx.core.approvals.Tier
import com.correx.core.events.events.ToolRequest
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.serialization.json.JsonObject

/**
 * One tool advertised by an MCP server, surfaced as a first-class Correx [Tool]/[ToolExecutor]. It
 * is exactly like any other tool from the kernel's point of view — so it inherits tier gating,
 * receipts, and event-recorded execution (#5) with zero special-casing, and replay reads the
 * recorded receipt instead of ever re-calling the server (#8/#9).
 *
 * Naming: `mcp__<server>__<remote>` — namespaced by server so two servers can advertise a tool of
 * the same name, and recognizable as MCP-backed at a glance (the same convention MCP hosts use).
 *
 * Capabilities are declared empty: an external MCP tool's side effects are opaque and cannot be
 * classified into Correx's FILE/NETWORK/SHELL taxonomy, so it does not claim any — safety comes from
 * [tier] (default T2 = approval-gated) rather than from a capability guess that would misfire the
 * plane-2 rules (e.g. NETWORK_ACCESS expects a NETWORK_TARGET param an MCP tool doesn't have).
 */
class McpTool(
    private val serverId: String,
    private val remoteName: String,
    override val description: String,
    override val parametersSchema: JsonObject,
    override val tier: Tier,
    private val client: McpClientHandle,
) : Tool, ToolExecutor {

    override val name: String = "mcp__${serverId}__$remoteName"
    override val requiredCapabilities: Set<ToolCapability> = emptySet()

    override fun validateRequest(request: ToolRequest): ValidationResult = ValidationResult.Valid

    override suspend fun execute(request: ToolRequest): ToolResult =
        runCatching { client.callTool(remoteName, argumentsOf(request.parameters)) }
            .fold(
                onSuccess = { result ->
                    if (result.isError) {
                        // A tool-reported failure (e.g. "package not found") — recoverable so it flows
                        // back into the ReAct loop as evidence rather than aborting the stage.
                        ToolResult.Failure(request.invocationId, result.text, recoverable = true)
                    } else {
                        ToolResult.Success(request.invocationId, result.text)
                    }
                },
                // A transport/protocol failure (server crashed, bad frame) is not the model's fault
                // and not fixable by retrying the same call: non-recoverable.
                onFailure = { ToolResult.Failure(request.invocationId, "MCP call failed: ${it.message}", recoverable = false) },
            )
}

/** The slice of [McpClient] an [McpTool] needs — narrowed to an interface so tools are unit-testable. */
interface McpClientHandle {
    suspend fun callTool(remoteName: String, arguments: JsonObject): McpToolCallOutcome
}

/** Transport-agnostic result an [McpClientHandle] hands back (decouples the tool from wire types). */
data class McpToolCallOutcome(val text: String, val isError: Boolean)
