package com.correx.infrastructure.tools.task

import com.correx.core.approvals.Tier
import com.correx.core.events.events.ToolRequest
import com.correx.core.events.types.ProjectId
import com.correx.core.events.types.TaskNoteAuthor
import com.correx.core.tasks.TaskService
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
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject

/** Agent-facing tool: create a task in a project's work graph. */
class TaskCreateTool(private val service: TaskService) : Tool, ToolExecutor {

    override val name: String = "task_create"
    override val description: String =
        "Open a task when work spans multiple sessions, needs handoff between runs, or the " +
            "operator asks to track it. Search first (task_search) to avoid duplicates. Don't " +
            "create one for a single self-contained edit you finish now. Returns the new id " +
            "(e.g. 'auth-142')."
    override val parametersSchema: JsonObject = buildJsonObject {
        put("type", "object")
        putJsonObject("properties") {
            putJsonObject("project") {
                put("type", "string")
                put("description", "Project key, e.g. 'auth'. The new task id is '<project>-<n>'.")
            }
            putJsonObject("title") {
                put("type", "string")
                put("description", "Short imperative title.")
            }
            putJsonObject("goal") {
                put("type", "string")
                put("description", "What 'done' looks like, in a sentence or two.")
            }
            putJsonObject("acceptance_criteria") {
                put("type", "array")
                putJsonObject("items") { put("type", "string") }
                put("description", "Concrete, verifiable criteria.")
            }
            putJsonObject("affected_paths") {
                put("type", "array")
                putJsonObject("items") { put("type", "string") }
                put("description", "Globs/paths this task is expected to touch.")
            }
            putJsonObject("force") {
                put("type", "boolean")
                put("description", "Create even if an active task with the same title exists. Default false.")
            }
            putJsonObject("force_reason") {
                put("type", "string")
                put("description", "Required when force=true: why a duplicate is acceptable. Recorded on the task.")
            }
        }
        put(
            "required",
            buildJsonArray {
                add(JsonPrimitive("project"))
                add(JsonPrimitive("title"))
                add(JsonPrimitive("goal"))
            },
        )
    }
    override val tier: Tier = Tier.T2
    override val requiredCapabilities: Set<ToolCapability> = emptySet()

    override fun validateRequest(request: ToolRequest): ValidationResult {
        request.stringParam("project") ?: return ValidationResult.Invalid("Missing 'project' (string).")
        request.stringParam("title") ?: return ValidationResult.Invalid("Missing 'title' (string).")
        request.stringParam("goal") ?: return ValidationResult.Invalid("Missing 'goal' (string).")
        return ValidationResult.Valid
    }

    override suspend fun execute(request: ToolRequest): ToolResult {
        val precheckFailure = precheck(request)
        if (precheckFailure != null) return precheckFailure
        val task = service.createTask(
            projectId = ProjectId(request.stringParam("project")!!),
            title = request.stringParam("title")!!,
            goal = request.stringParam("goal")!!,
            acceptanceCriteria = request.listParam("acceptance_criteria"),
            affectedPaths = request.listParam("affected_paths"),
        )
        // Provenance: tie the new task to the session that spawned it so its context bundle
        // can show the originating run.
        service.linkOriginSession(task.taskId, request)
        // A forced creation bypassed the duplicate guard — record why, for the audit trail.
        if (request.boolParam("force")) {
            request.stringParam("force_reason")?.let {
                service.addNote(task.taskId, TaskNoteAuthor.AGENT, "[force] created despite the duplicate guard: $it")
            }
        }
        return ToolResult.Success(
            invocationId = request.invocationId,
            output = "Created ${task.taskId.value}: ${task.state.title}",
            metadata = mapOf("taskId" to task.taskId.value, "status" to task.state.status.name),
        )
    }

    /**
     * Required-field validation; then either a force-reason requirement (force=true must carry a
     * force_reason, recorded after creation) or the duplicate-title guard. Null when OK to create.
     */
    private fun precheck(request: ToolRequest): ToolResult.Failure? {
        val invalid = validateRequest(request) as? ValidationResult.Invalid
        if (invalid != null) return ToolResult.Failure(request.invocationId, invalid.reason, recoverable = false)
        if (request.boolParam("force")) {
            if (request.stringParam("force_reason") == null) {
                return fail(request, "force=true requires 'force_reason' explaining why a duplicate is acceptable.")
            }
            return null
        }
        val duplicates = service.findDuplicates(
            ProjectId(request.stringParam("project")!!),
            request.stringParam("title")!!,
        )
        return duplicates.takeIf { it.isNotEmpty() }?.let {
            val listed = it.joinToString(", ") { d ->
                "${d.taskId.value} '${d.state.title.orEmpty()}' [${d.state.status.name}]"
            }
            fail(
                request,
                "Possible duplicate(s): $listed. Reuse one (task_context/task_update), " +
                    "or force=true with force_reason.",
            )
        }
    }

    private fun fail(request: ToolRequest, reason: String): ToolResult.Failure =
        ToolResult.Failure(request.invocationId, reason, recoverable = false)
}
