"""MCP Security Audit Scanner — Core scanning engine.

Coordinates rule execution, file scanning, and result aggregation.
Supports both Python (AST-based) and TypeScript/JavaScript (regex-based)
scanning with automatic language detection.
"""

from __future__ import annotations

import ast
import time
from pathlib import Path
from typing import Sequence

from .models import Finding, ScanResult, ScanSummary, Severity
from .rules import Rule, get_all_rules
from .ts_scanner import TSSecurityScanner, is_ts_js_file, TS_JS_EXTENSIONS

# Python file extensions
PY_EXTENSIONS = {".py", ".pyw"}

# All supported extensions
ALL_EXTENSIONS = PY_EXTENSIONS | TS_JS_EXTENSIONS


def detect_language(filepath: Path) -> str:
    """Detect the programming language from a file extension.

    Args:
        filepath: Path to the source file.

    Returns:
        Language string: 'python', 'typescript', or 'unknown'.
    """
    ext = filepath.suffix.lower()
    if ext in PY_EXTENSIONS:
        return "python"
    if ext in TS_JS_EXTENSIONS:
        return "typescript"
    return "unknown"


class MCPSecurityScanner:
    """Static security scanner for MCP servers.

    Scans Python source files using AST analysis and regex pattern matching,
    and TypeScript/JavaScript files using regex pattern matching.
    Auto-detects language from file extensions. Zero external dependencies.
    """

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

        Args:
            rules: Optional list of Python rules. If None, all built-in rules.
        """
        self.rules: list[Rule] = rules if rules is not None else get_all_rules()
        self._ts_scanner = TSSecurityScanner()

    def scan_file(self, filepath: Path) -> ScanResult:
        """Scan a single source file and return findings.

        Auto-detects language from extension. Delegates to the
        appropriate scanner (Python AST or TS/JS regex).

        Args:
            filepath: Path to the source file to scan.

        Returns:
            ScanResult with all findings for this file.
        """
        lang = detect_language(filepath)

        if lang == "typescript":
            return self._ts_scanner.scan_file(filepath)

        return self._scan_python_file(filepath)

    def _scan_python_file(self, filepath: Path) -> ScanResult:
        """Scan a single Python file using AST analysis.

        Args:
            filepath: Path to the Python 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))

        try:
            tree: ast.Module = ast.parse(source, filename=str(filepath))
        except SyntaxError as e:
            result.parse_error = str(e)
            result.findings.append(Finding(
                rule_id="PARSE-001",
                severity=Severity.INFO,
                title="Error de parseo",
                description=f"No se pudo parsear: {e}",
                file=str(filepath),
                line=e.lineno or 0,
            ))
            result.scan_time_ms = (time.monotonic() - start) * 1000
            return result

        for rule in self.rules:
            findings: list[Finding] = rule.check(tree, 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 supported files in a directory recursively.

        Scans both .py and .ts/.js files, auto-detecting language.

        Args:
            dirpath: Path to the directory to scan.

        Returns:
            List of ScanResult, one per file.
        """
        results: list[ScanResult] = []

        # Scan Python files
        for py_file in sorted(dirpath.rglob("*.py")):
            results.append(self._scan_python_file(py_file))

        # Scan TypeScript/JavaScript files
        ts_results = self._ts_scanner.scan_directory(dirpath)
        results.extend(ts_results)

        return results

    def scan(self, target: Path) -> ScanSummary:
        """Scan a file or directory and return an 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
