"""GO-CMD-001: Command Injection detection for Go.

Detects usage of exec.Command, exec.CommandContext, and os.StartProcess
with unsanitized input that enables command injection.

NOTE: This module contains string references to unsafe shell functions
because it is a SECURITY SCANNER that detects these patterns in target
code. These are detection signatures, not actual usage.
"""

from __future__ import annotations

import re

from . import GoRule
from ..models import Finding, Severity

# Patterns for dangerous exec functions
EXEC_COMMAND_PATTERN = re.compile(
    r'\bexec\.(?:Command|CommandContext)\s*\('
)
OS_STARTPROCESS_PATTERN = re.compile(
    r'\bos\.StartProcess\s*\('
)
SYSCALL_EXEC_PATTERN = re.compile(
    r'\bsyscall\.Exec\s*\('
)
# String formatting/concatenation in exec args
FMT_SPRINTF_IN_EXEC = re.compile(
    r'exec\.Command(?:Context)?\s*\([^)]*fmt\.Sprintf'
)
CONCAT_IN_EXEC = re.compile(
    r'exec\.Command(?:Context)?\s*\([^)]*\+'
)
# Shell execution via sh -c or bash -c
SHELL_EXEC_PATTERN = re.compile(
    r'exec\.Command(?:Context)?\s*\(\s*["\'](?:sh|bash|/bin/(?:sh|bash))["\']\s*,\s*["\'](?:-c)["\']\s*,'
)


class GoCommandInjectionRule(GoRule):
    """Detects command injection patterns in Go."""

    @property
    def rule_id(self) -> str:
        return "GO-CMD-001"

    @property
    def name(self) -> str:
        return "Command Injection (exec.Command)"

    @property
    def severity(self) -> Severity:
        return Severity.CRITICAL

    @property
    def cwe(self) -> str:
        return "CWE-78"

    @property
    def description(self) -> str:
        return (
            "exec.Command/exec.CommandContext with unsanitized input "
            "enables command injection in Go MCP servers."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "exec.Command/exec.CommandContext with unsanitized input "
            "enables command injection in Go MCP servers.\n\n"
            "In Go, os/exec.Command() passes commands directly to the OS.\n"
            "If any argument comes from user input without sanitization,\n"
            "an attacker can inject arbitrary commands.\n\n"
            "Examples of vulnerable code:\n"
            '  exec.Command("sh", "-c", userInput)\n'
            '  exec.Command(fmt.Sprintf("grep %s file.txt", pattern))\n\n'
            "How to fix:\n"
            "  - Never use sh -c with user input\n"
            "  - Pass arguments as separate parameters to exec.Command\n"
            "  - Validate and sanitize all user-provided arguments\n"
            "  - Use allowlists for permitted commands"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for command injection patterns."""
        findings: list[Finding] = []

        # Shell exec via sh -c / bash -c
        for lineno, line, _match in self._search_lines(source, SHELL_EXEC_PATTERN):
            findings.append(self._make_finding(
                lineno, line,
                "exec.Command with shell (sh/bash -c) — high injection risk",
            ))

        # fmt.Sprintf in exec args
        for lineno, line, _match in self._search_lines(source, FMT_SPRINTF_IN_EXEC):
            # Skip if already caught by shell pattern
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "exec.Command with fmt.Sprintf (dynamic command construction)",
            ))

        # String concatenation in exec args
        for lineno, line, _match in self._search_lines(source, CONCAT_IN_EXEC):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "exec.Command with string concatenation",
            ))

        # syscall.Exec usage
        for lineno, line, _match in self._search_lines(source, SYSCALL_EXEC_PATTERN):
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.HIGH,
                title="Low-level syscall.Exec usage",
                description="syscall.Exec bypasses Go's exec safety layer.",
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation="Use os/exec.Command instead of syscall.Exec.",
                cwe=self.cwe,
            ))

        # os.StartProcess
        for lineno, line, _match in self._search_lines(source, OS_STARTPROCESS_PATTERN):
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.HIGH,
                title="os.StartProcess usage",
                description="os.StartProcess is a low-level API prone to injection.",
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation="Use os/exec.Command with separate arguments.",
                cwe=self.cwe,
            ))

        return findings

    def _make_finding(self, lineno: int, line: str, desc: str) -> Finding:
        """Create a command injection finding."""
        return Finding(
            rule_id=self.rule_id,
            severity=self.severity,
            title="Command Injection",
            description=desc,
            file="",
            line=lineno,
            code_snippet=line.strip()[:80],
            recommendation=(
                "Pass arguments as separate parameters to exec.Command. "
                "Never use sh -c with user input."
            ),
            cwe=self.cwe,
        )
