"""TS-DESER-001: Unsafe Deserialization for TypeScript/JavaScript.

Detects eval(), Function constructor, and other unsafe deserialization
patterns in TypeScript/JavaScript MCP servers.

NOTE: This module contains regex patterns that reference dangerous
functions (eval, Function, etc.) because it is a SECURITY SCANNER
that detects these patterns in target code. These are detection
signatures, not actual usage of those functions.
"""

from __future__ import annotations

import re

from . import TSRule
from ..models import Finding, Severity

# Detection signatures for dangerous deserialization functions.
# These are regex patterns the scanner looks for — not executed.
EVAL_PATTERN = re.compile(
    r'\beval\s*\('
)
FUNC_CTOR_PATTERN = re.compile(
    r'\bnew\s+Function\s*\('
)
SETTIMEOUT_STRING = re.compile(
    r'\b(?:setTimeout|setInterval)\s*\(\s*[\'"`]'
)
JSON_PARSE_UNTRUSTED = re.compile(
    r'JSON\.parse\s*\(\s*(?:req\.|args\.|params\.|'
    r'body\.|input\.|data\.|message\.|payload\.|'
    r'event\.|request\.)'
)
UNSERIALIZE = re.compile(
    r'\b(?:unserialize|deserialize|node-serialize)\s*\('
)
VM_RUN = re.compile(
    r'\bvm\.(?:runInContext|runInNewContext|runInThisContext|'
    r'createContext|Script)\s*\('
)


class TSUnsafeDeserializationRule(TSRule):
    """Detects unsafe deserialization in TypeScript/JavaScript."""

    @property
    def rule_id(self) -> str:
        return "TS-DESER-001"

    @property
    def name(self) -> str:
        return "Unsafe Deserialization"

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

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

    @property
    def description(self) -> str:
        return (
            "Detected eval(), Function constructor, or unsafe "
            "deserialization of untrusted data."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "Detected eval(), Function constructor, or unsafe "
            "deserialization of untrusted data.\n\n"
            "These patterns allow arbitrary code execution when\n"
            "processing user-controlled input.\n\n"
            "How to fix:\n"
            "  - Never pass untrusted input to eval()\n"
            "  - Use JSON.parse() with try/catch for data parsing\n"
            "  - Use vm2 or isolated-vm for sandboxed execution\n"
            "  - Validate input before JSON.parse()"
        )

    def check(self, source: str) -> list[Finding]:
        """Check for unsafe deserialization patterns."""
        findings: list[Finding] = []
        lines = self._get_lines(source)

        for i, line in enumerate(lines, 1):
            if self._is_comment_line(line):
                continue

            # eval() usage
            if EVAL_PATTERN.search(line):
                findings.append(self._make_finding(
                    i, line,
                    "eval() usage — arbitrary code execution risk.",
                    Severity.CRITICAL,
                ))
                continue

            # Function constructor
            if FUNC_CTOR_PATTERN.search(line):
                findings.append(self._make_finding(
                    i, line,
                    "Function constructor — dynamic code execution risk.",
                    Severity.CRITICAL,
                ))
                continue

            # setTimeout/setInterval with string arg
            if SETTIMEOUT_STRING.search(line):
                findings.append(self._make_finding(
                    i, line,
                    "setTimeout/setInterval with string argument "
                    "(implicit eval).",
                    Severity.HIGH,
                ))
                continue

            # vm module
            if VM_RUN.search(line):
                findings.append(self._make_finding(
                    i, line,
                    "vm module execution of potentially untrusted code.",
                    Severity.HIGH,
                ))
                continue

            # JSON.parse of untrusted input (lower severity)
            if JSON_PARSE_UNTRUSTED.search(line):
                context_start = max(0, i - 3)
                context = "\n".join(lines[context_start:i])
                if "try" not in context:
                    findings.append(Finding(
                        rule_id=self.rule_id,
                        severity=Severity.LOW,
                        title="JSON.parse of untrusted input",
                        description=(
                            "JSON.parse() on user input without "
                            "try/catch error handling."
                        ),
                        file="",
                        line=i,
                        code_snippet=line.strip()[:80],
                        recommendation=(
                            "Wrap JSON.parse() in try/catch and "
                            "validate the parsed structure."
                        ),
                        cwe="CWE-502",
                        confidence="LOW",
                    ))

        return findings

    def _make_finding(
        self,
        lineno: int,
        line: str,
        desc: str,
        severity: Severity,
    ) -> Finding:
        """Create an unsafe deserialization finding."""
        return Finding(
            rule_id=self.rule_id,
            severity=severity,
            title="Unsafe Deserialization",
            description=desc,
            file="",
            line=lineno,
            code_snippet=line.strip()[:80],
            recommendation=(
                "Avoid eval/Function constructor. Use safe "
                "alternatives for data processing."
            ),
            cwe=self.cwe,
        )
