"""GO-PATH-001: Path Traversal detection for Go.

Detects filepath.Join and os.Open with unsanitized user input
that enables path traversal attacks.
"""

from __future__ import annotations

import re

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

# filepath.Join with variable (potential user input)
FILEPATH_JOIN_PATTERN = re.compile(
    r'\bfilepath\.Join\s*\('
)
# os.Open/os.Create/os.OpenFile with concatenation or fmt
OS_FILE_OPEN_PATTERN = re.compile(
    r'\bos\.(?:Open|Create|OpenFile|ReadFile|WriteFile|MkdirAll|Remove|RemoveAll)\s*\('
)
# ioutil.ReadFile / ioutil.WriteFile (deprecated but still used)
IOUTIL_PATTERN = re.compile(
    r'\bioutil\.(?:ReadFile|WriteFile)\s*\('
)
# String concatenation in file paths
PATH_CONCAT_PATTERN = re.compile(
    r'(?:os\.(?:Open|Create|OpenFile|ReadFile|WriteFile)|filepath\.Join)\s*\([^)]*\+'
)
# fmt.Sprintf for path construction
PATH_SPRINTF_PATTERN = re.compile(
    r'(?:os\.(?:Open|Create|OpenFile|ReadFile|WriteFile))\s*\(\s*fmt\.Sprintf'
)
# filepath.Clean NOT called (negative check — presence of Join without Clean)
FILEPATH_CLEAN_PATTERN = re.compile(
    r'\bfilepath\.Clean\s*\('
)


class GoPathTraversalRule(GoRule):
    """Detects path traversal patterns in Go."""

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

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

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

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

    @property
    def description(self) -> str:
        return (
            "filepath.Join with unsanitized input does NOT prevent "
            "path traversal (../) in Go."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "filepath.Join with unsanitized input does NOT prevent "
            "path traversal (../) in Go.\n\n"
            "Unlike some frameworks, Go's filepath.Join does NOT sanitize\n"
            "directory traversal sequences. filepath.Join('base', '../etc/passwd')\n"
            "resolves to '../etc/passwd', enabling file access outside the\n"
            "intended directory.\n\n"
            "Examples of vulnerable code:\n"
            '  path := filepath.Join(baseDir, userInput)\n'
            '  data, _ := os.ReadFile(filepath.Join("/data", req.Filename))\n\n'
            "How to fix:\n"
            "  - Use filepath.Clean then verify the result starts with baseDir\n"
            "  - Use strings.HasPrefix(resolved, baseDir) after filepath.Abs\n"
            "  - Reject inputs containing .. or absolute paths"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for path traversal patterns."""
        findings: list[Finding] = []
        has_clean = bool(FILEPATH_CLEAN_PATTERN.search(source))

        # filepath.Join without filepath.Clean nearby
        for lineno, line, _match in self._search_lines(source, FILEPATH_JOIN_PATTERN):
            # If there's no filepath.Clean in the entire file, flag it
            if not has_clean:
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="Path Traversal",
                    description=(
                        "filepath.Join without filepath.Clean — "
                        "does not prevent ../ traversal."
                    ),
                    file="",
                    line=lineno,
                    code_snippet=line.strip()[:80],
                    recommendation=(
                        "Use filepath.Clean + strings.HasPrefix to validate "
                        "the resolved path stays within the intended directory."
                    ),
                    cwe=self.cwe,
                ))

        # String concatenation in file path operations
        for lineno, line, _match in self._search_lines(source, PATH_CONCAT_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.CRITICAL,
                title="Path Traversal (string concatenation)",
                description=(
                    "File path built with string concatenation — "
                    "high traversal risk."
                ),
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation=(
                    "Use filepath.Join with filepath.Clean and validate "
                    "the result."
                ),
                cwe=self.cwe,
            ))

        # fmt.Sprintf in file operations
        for lineno, line, _match in self._search_lines(source, PATH_SPRINTF_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.CRITICAL,
                title="Path Traversal (fmt.Sprintf)",
                description=(
                    "File path built with fmt.Sprintf — "
                    "high traversal risk."
                ),
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation=(
                    "Avoid fmt.Sprintf for file paths. Use filepath.Join "
                    "with proper validation."
                ),
                cwe=self.cwe,
            ))

        return findings
