package com.correx.core.toolintent.rules

import com.correx.core.events.events.ToolCallObservation
import com.correx.core.events.risk.RiskAction
import com.correx.core.toolintent.ToolCallAssessment
import com.correx.core.toolintent.ToolCallAssessmentInput
import com.correx.core.toolintent.ToolCallRule
import com.correx.core.toolintent.maxAction
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.validation.model.ValidationIssue
import com.correx.core.validation.model.ValidationSeverity
import java.nio.file.Path

/**
 * Effect-based path containment. Dispatches on FILE_READ / FILE_WRITE. For every
 * path-like string argument, records {path, resolved, exists, inWorkspace, privileged}
 * and raises: privileged → BLOCK, outside-workspace → PROMPT_USER.
 * Symlink escapes are caught because resolution goes through WorldProbe.resolveReal
 * (toRealPath). Fail-closed: anything that doesn't resolve into the workspace is
 * treated as outside.
 */
@Suppress("ReturnCount")
class PathContainmentRule : ToolCallRule {

    override fun appliesTo(capabilities: Set<ToolCapability>): Boolean =
        ToolCapability.FILE_WRITE in capabilities || ToolCapability.FILE_READ in capabilities

    override fun assess(input: ToolCallAssessmentInput): ToolCallAssessment {
        val workspaceReal = input.probe.resolveReal(input.workspace.workspaceRoot)
        val privilegedReal = input.workspace.privilegedLocations.map { input.probe.resolveReal(it) }

        val issues = mutableListOf<ValidationIssue>()
        val observations = mutableListOf<ToolCallObservation>()
        var disposition = RiskAction.PROCEED

        for (raw in candidatePathStrings(input.paramRoles, input.request.parameters)) {
            val candidate = Path.of(raw)
            val resolvedInput =
                if (candidate.isAbsolute) candidate else input.workspace.workspaceRoot.resolve(candidate)
            val resolvedReal = input.probe.resolveReal(resolvedInput)
            val exists = input.probe.exists(resolvedInput)
            val privileged = privilegedReal.any { resolvedReal.startsWith(it) }
            val inWorkspace = resolvedReal.startsWith(workspaceReal)

            observations += ToolCallObservation(
                ruleCode = RULE_CODE,
                facts = mapOf(
                    "path" to raw,
                    "resolved" to resolvedReal.toString(),
                    "exists" to exists.toString(),
                    "inWorkspace" to inWorkspace.toString(),
                    "privileged" to privileged.toString(),
                ),
            )

            when {
                privileged -> {
                    issues += ValidationIssue(
                        code = "PRIVILEGED_LOCATION",
                        message = "Tool '${input.request.toolName}' targets privileged location: $raw",
                        severity = ValidationSeverity.ERROR,
                    )
                    disposition = maxAction(disposition, RiskAction.BLOCK)
                }
                !inWorkspace -> {
                    issues += ValidationIssue(
                        code = "PATH_OUTSIDE_WORKSPACE",
                        message = "Tool '${input.request.toolName}' targets path outside workspace: $raw",
                        severity = ValidationSeverity.WARNING,
                    )
                    disposition = maxAction(disposition, RiskAction.PROMPT_USER)
                }
            }
        }

        return ToolCallAssessment(issues = issues, observations = observations, disposition = disposition)
    }

    private companion object {
        const val RULE_CODE = "PATH_CONTAINMENT"
    }
}
