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.ParamRole
import com.correx.core.tools.contract.ToolCapability
import com.correx.core.validation.model.ValidationIssue
import com.correx.core.validation.model.ValidationSeverity

/**
 * Classifies shell/process executions by the invoked executable. Dispatches on
 * SHELL_EXEC / PROCESS_SPAWN. An interpreter invocation (python, bash, node, …)
 * runs arbitrary code and is elevated to PROMPT_USER; a plain command proceeds.
 * Executable detection is generic: it tokenizes every string parameter (covers
 * both an `argv` JSON-array string and a raw command line) and inspects the
 * leading token's basename — no tool-name or parameter-name hardcoding.
 */
class ExecInterpreterRule(interpreters: Set<String>) : ToolCallRule {

    private val interpreters: Set<String> = interpreters.map { it.lowercase() }.toSet()

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

    override fun assess(input: ToolCallAssessmentInput): ToolCallAssessment {
        val issues = mutableListOf<ValidationIssue>()
        val observations = mutableListOf<ToolCallObservation>()
        var disposition = RiskAction.PROCEED

        for (executable in executables(input)) {
            val basename = executable.substringAfterLast('/').lowercase()
            val isInterpreter = basename in interpreters
            observations += ToolCallObservation(
                ruleCode = RULE_CODE,
                facts = mapOf("executable" to executable, "isInterpreter" to isInterpreter.toString()),
            )
            if (isInterpreter) {
                issues += ValidationIssue(
                    code = "INTERPRETER_EXECUTION",
                    message = "Tool '${input.request.toolName}' invokes interpreter '$basename'",
                    severity = ValidationSeverity.WARNING,
                )
                disposition = maxAction(disposition, RiskAction.PROMPT_USER)
            }
        }
        return ToolCallAssessment(issues = issues, observations = observations, disposition = disposition)
    }

    private fun executables(input: ToolCallAssessmentInput): List<String> {
        val declared = input.paramRoles.filterValues { it == ParamRole.EXEC_COMMAND }.keys
        val raws = if (declared.isNotEmpty()) {
            declared.flatMap { extractParamStrings(input.request.parameters[it]) }
        } else {
            input.request.parameters.values.filterIsInstance<String>()
        }
        return raws.mapNotNull { leadingToken(it) }
    }

    private fun leadingToken(raw: String): String? =
        raw.split(TOKEN_DELIMITERS).firstOrNull { it.isNotBlank() }

    private companion object {
        const val RULE_CODE = "EXEC_INTERPRETER"
        val TOKEN_DELIMITERS = Regex("[\\s,\\[\\]\"']+")
    }
}
