"""TS-PATH-001: Path Traversal detection for TypeScript/JavaScript.

Detects unsanitized user input in file system operations and
directory traversal patterns (../) in Node.js/TS MCP servers.
"""

from __future__ import annotations

import re

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

# fs operations that take a file path argument
FS_READ_PATTERN = re.compile(
    r'\b(?:fs|fsPromises)\.'
    r'(?:readFile|readFileSync|readdir|readdirSync|'
    r'createReadStream|access|accessSync|stat|statSync|'
    r'exists|existsSync|open|openSync)\s*\('
)
FS_WRITE_PATTERN = re.compile(
    r'\b(?:fs|fsPromises)\.'
    r'(?:writeFile|writeFileSync|appendFile|appendFileSync|'
    r'createWriteStream|mkdir|mkdirSync|unlink|unlinkSync|'
    r'rmdir|rmdirSync|rm|rmSync|rename|renameSync|copyFile|'
    r'copyFileSync)\s*\('
)
PATH_JOIN_PATTERN = re.compile(
    r'\bpath\.(?:join|resolve)\s*\([^)]*(?:\breq\b|\bargs\b|\bparams\b|'
    r'\binput\b|\bquery\b|\bbody\b|\bfilename\b|\bfileName\b)'
)
DOTDOT_PATTERN = re.compile(
    r'''['"`]\.\./|\.\.['"`]|\.\.[/\\]'''
)
UNSANITIZED_PATH_PATTERN = re.compile(
    r'\b(?:fs|fsPromises)\.\w+\s*\(\s*(?:req\.|args\.|params\.|'
    r'input\.|query\.|body\.|\w+\.filename|'
    r'`[^`]*\$\{)'
)


class TSPathTraversalRule(TSRule):
    """Detects path traversal patterns in TypeScript/JavaScript."""

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

    @property
    def name(self) -> str:
        return "Path Traversal"

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

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

    @property
    def description(self) -> str:
        return (
            "File system operations with unsanitized paths may allow "
            "directory traversal attacks."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "File system operations with unsanitized paths may allow "
            "directory traversal attacks.\n\n"
            "Examples of vulnerable code:\n"
            "  fs.readFileSync(req.query.path);\n"
            "  fs.readFile(`./data/${args.filename}`);\n"
            "  const file = path.join(base, userInput);\n\n"
            "How to fix:\n"
            "  - Validate and normalize paths with path.resolve()\n"
            "  - Check that resolved path stays within allowed directory\n"
            "  - Reject paths containing '..' sequences\n"
            "  - Use a whitelist of allowed file names"
        )

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

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

            # fs operation with user input directly
            if UNSANITIZED_PATH_PATTERN.search(line):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="Path Traversal (unsanitized fs input)",
                    description=(
                        "File system operation with user-controlled path."
                    ),
                    file="",
                    line=i,
                    code_snippet=line.strip()[:80],
                    recommendation=(
                        "Validate path with path.resolve() and check it "
                        "stays within the allowed directory."
                    ),
                    cwe=self.cwe,
                ))
                continue

            # path.join/resolve with user input
            if PATH_JOIN_PATTERN.search(line):
                # Check if there's a subsequent bounds check
                next_lines = "\n".join(
                    lines[i:min(i + 3, len(lines))]
                )
                if not re.search(
                    r'startsWith|isAbsolute|includes\s*\(\s*[\'"]\.\.',
                    next_lines
                ):
                    findings.append(Finding(
                        rule_id=self.rule_id,
                        severity=Severity.MEDIUM,
                        title="Path Traversal (path.join with user input)",
                        description=(
                            "path.join/resolve with user input without "
                            "bounds checking."
                        ),
                        file="",
                        line=i,
                        code_snippet=line.strip()[:80],
                        recommendation=(
                            "After path.join, verify the resolved path "
                            "starts with the base directory."
                        ),
                        cwe=self.cwe,
                    ))

        return findings
