"""TypeScript/JavaScript MCP Security Scanner.

Scans TS/JS source files using regex-based pattern matching
to detect security vulnerabilities. Zero external dependencies.
"""

from __future__ import annotations

import time
from pathlib import Path

from .deobfuscate import deobfuscate
from .models import Finding, ScanResult, ScanSummary, Severity
from .ts_rules import TSRule, get_all_ts_rules

# File extensions recognized as TypeScript/JavaScript
TS_JS_EXTENSIONS = {".ts", ".js", ".tsx", ".jsx", ".mts", ".mjs", ".cts", ".cjs"}

# Patterns indicating an MCP server (for informational context)
MCP_SDK_PATTERNS = (
    "@modelcontextprotocol/sdk",
    "mcp-framework",
    "@anthropic-ai/sdk",
    "McpServer",
    "MCPServer",
    "Server(",
    "createServer",
    ".tool(",
    ".addTool(",
    "setRequestHandler",
)


def is_ts_js_file(filepath: Path) -> bool:
    """Check if a file is a TypeScript or JavaScript file.

    Args:
        filepath: Path to check.

    Returns:
        True if the file has a TS/JS extension.
    """
    return filepath.suffix.lower() in TS_JS_EXTENSIONS


def detect_mcp_patterns(source: str) -> list[str]:
    """Detect MCP SDK usage patterns in source code.

    Args:
        source: Source code to scan.

    Returns:
        List of detected MCP SDK patterns.
    """
    found: list[str] = []
    for pattern in MCP_SDK_PATTERNS:
        if pattern in source:
            found.append(pattern)
    return found


class TSSecurityScanner:
    """Static security scanner for MCP servers in TypeScript/JavaScript.

    Uses regex-based pattern matching to detect security vulnerabilities.
    No external dependencies (no tree-sitter, no npm).
    """

    def __init__(
        self,
        rules: list[TSRule] | None = None,
        deobfuscate_enabled: bool = True,
    ) -> None:
        """Initialize scanner with rules.

        Args:
            rules: Optional list of rules. If None, all built-in rules.
            deobfuscate_enabled: Run deobfuscation layer before scanning.
        """
        self.rules: list[TSRule] = (
            rules if rules is not None else get_all_ts_rules()
        )
        self.deobfuscate_enabled: bool = deobfuscate_enabled

    def scan_file(self, filepath: Path) -> ScanResult:
        """Scan a single TypeScript/JavaScript file.

        Args:
            filepath: Path to the TS/JS file to scan.

        Returns:
            ScanResult with all findings for this file.
        """
        start: float = time.monotonic()
        source: str = filepath.read_text(encoding="utf-8", errors="replace")
        lines: list[str] = source.splitlines()
        result = ScanResult(file=str(filepath), lines_scanned=len(lines))

        # Deobfuscation layer
        if self.deobfuscate_enabled:
            source, deob_findings = deobfuscate(source)
            result.deobfuscation_findings = deob_findings
            for df in deob_findings:
                result.findings.append(Finding(
                    rule_id=f"DEOB-{df.category.upper()[:4]}",
                    severity=Severity.HIGH if df.category in (
                        "unicode_tag", "bidi"
                    ) else Severity.MEDIUM,
                    title=f"Obfuscation detected: {df.category}",
                    description=df.description,
                    file=str(filepath),
                    line=df.line,
                ))

        for rule in self.rules:
            findings: list[Finding] = rule.check(source)
            for f in findings:
                f.file = str(filepath)
            result.findings.extend(findings)

        result.scan_time_ms = (time.monotonic() - start) * 1000
        return result

    def scan_directory(self, dirpath: Path) -> list[ScanResult]:
        """Scan all TS/JS files in a directory recursively.

        Args:
            dirpath: Path to the directory to scan.

        Returns:
            List of ScanResult, one per file.
        """
        results: list[ScanResult] = []
        for ext in sorted(TS_JS_EXTENSIONS):
            for ts_file in sorted(dirpath.rglob(f"*{ext}")):
                # Skip node_modules and dist directories
                parts = ts_file.parts
                if "node_modules" in parts or "dist" in parts:
                    continue
                results.append(self.scan_file(ts_file))
        return results

    def scan(self, target: Path) -> ScanSummary:
        """Scan a file or directory and return aggregate summary.

        Args:
            target: Path to a file or directory.

        Returns:
            ScanSummary with all results aggregated.
        """
        start: float = time.monotonic()

        if target.is_file():
            results: list[ScanResult] = [self.scan_file(target)]
        elif target.is_dir():
            results = self.scan_directory(target)
        else:
            raise FileNotFoundError(f"Target not found: {target}")

        summary = ScanSummary(results=results)
        summary.total_scan_time_ms = (time.monotonic() - start) * 1000
        return summary
