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

/**
 * Shell-command-as-file-content gate. Dispatches on FILE_WRITE: a model lacking a shell tool has
 * been observed "faking" one by writing a file whose entire content is a shell command (e.g. a file
 * named `tasks` containing only `mkdir -p frontend/src/components/tasks`) instead of learning that
 * file_write already creates parent directories. The heuristic is deliberately narrow to avoid
 * flagging legitimate single-line files: it only fires when the content is a single short line that
 * *starts* with a directory/file-management shell verb, AND the write target's extension is one that
 * would never legitimately hold a bare shell command (source/doc/data files) — shell scripts
 * (`.sh`/`.bash`/etc.), known build-tool files by basename (e.g. `Makefile`, `Dockerfile`), and any
 * multi-line content are exempt.
 */
class ShellInFileContentRule : ToolCallRule {

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

    override fun assess(input: ToolCallAssessmentInput): ToolCallAssessment {
        val pathString = input.request.parameters["path"] as? String
        val content = input.request.parameters["content"] as? String

        if (pathString == null || content == null) {
            return ToolCallAssessment()
        }

        val trimmed = content.trim()
        val isSingleLine = trimmed.isNotBlank() && !trimmed.contains('\n')
        val leadingVerb = SHELL_VERBS.firstOrNull { verb ->
            trimmed.startsWith(verb) && (trimmed.length == verb.length || trimmed[verb.length].isWhitespace())
        }
        val fileName = pathString.substringAfterLast('/')
        val extension = fileName.substringAfterLast('.', missingDelimiterValue = "")
        val isExemptTarget = extension.lowercase() in EXEMPT_EXTENSIONS ||
            fileName.lowercase() in EXEMPT_BASENAMES

        val looksLikeShellCommand = isSingleLine && leadingVerb != null && !isExemptTarget

        val observations = listOf(
            ToolCallObservation(
                ruleCode = RULE_CODE,
                facts = mapOf(
                    "path" to pathString,
                    "singleLine" to isSingleLine.toString(),
                    "leadingVerb" to (leadingVerb ?: ""),
                    "extension" to extension,
                    "flagged" to looksLikeShellCommand.toString(),
                ),
            ),
        )

        if (!looksLikeShellCommand) {
            return ToolCallAssessment(observations = observations)
        }

        val issue = ValidationIssue(
            code = RULE_CODE,
            message = "[$RULE_CODE] file_write content looks like a shell command " +
                "('${trimmed.take(SNIPPET_LENGTH)}'), not file content. To create a directory, just " +
                "write your file at the nested path — parents are auto-created. Do not write shell " +
                "commands into files.",
            severity = ValidationSeverity.ERROR,
        )

        return ToolCallAssessment(
            issues = listOf(issue),
            observations = observations,
            disposition = maxAction(RiskAction.PROCEED, RiskAction.BLOCK),
        )
    }

    private companion object {
        const val RULE_CODE = "SHELL_IN_FILE"
        const val SNIPPET_LENGTH = 60
        val SHELL_VERBS = listOf("mkdir", "rm", "cp", "mv", "touch", "cat", "echo", "chmod", "ls")
        val EXEMPT_EXTENSIONS = setOf("sh", "bash", "zsh", "ps1", "bat", "cmd")
        val EXEMPT_BASENAMES = setOf("makefile", "dockerfile", "vagrantfile", "rakefile", "gemfile", "procfile")
    }
}
