"""GO-SSRF-001: Server-Side Request Forgery detection for Go.

Detects http.Get/Post/Do with user-controlled URLs that enable
SSRF attacks.
"""

from __future__ import annotations

import re

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

# http.Get/Post/Head with variable URL
HTTP_GET_PATTERN = re.compile(
    r'\bhttp\.(?:Get|Post|Head|PostForm)\s*\('
)
# http.NewRequest with variable URL
HTTP_NEWREQUEST_PATTERN = re.compile(
    r'\bhttp\.NewRequest(?:WithContext)?\s*\('
)
# URL from user input (fmt.Sprintf in URL)
URL_SPRINTF_PATTERN = re.compile(
    r'http\.(?:Get|Post|Head|NewRequest)\s*\([^)]*fmt\.Sprintf'
)
# URL from string concatenation
URL_CONCAT_PATTERN = re.compile(
    r'http\.(?:Get|Post|Head|NewRequest)\s*\([^)]*\+'
)
# net.Dial with variable address
NET_DIAL_PATTERN = re.compile(
    r'\bnet\.(?:Dial|DialTimeout|DialTCP|DialUDP)\s*\('
)
# URL parsed from request (common SSRF source)
URL_FROM_REQUEST = re.compile(
    r'r\.(?:URL|Form|PostForm|Body|FormValue|QueryParam|Query\(\)).*(?:http\.Get|http\.Post|client\.Do|http\.NewRequest)',
)


class GoSSRFRule(GoRule):
    """Detects SSRF patterns in Go."""

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

    @property
    def name(self) -> str:
        return "SSRF (http.Get/Post with user input)"

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

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

    @property
    def description(self) -> str:
        return (
            "HTTP request with user-controlled URL enables "
            "Server-Side Request Forgery (SSRF)."
        )

    @property
    def detailed_description(self) -> str:
        return (
            "HTTP request with user-controlled URL enables SSRF.\n\n"
            "If the URL for http.Get/Post comes from user input,\n"
            "an attacker can make the server request internal resources\n"
            "(metadata APIs, internal services, cloud credentials).\n\n"
            "Examples of vulnerable code:\n"
            '  resp, _ := http.Get(r.FormValue("url"))\n'
            '  http.Post(fmt.Sprintf("http://%s/api", host), ...)\n\n'
            "How to fix:\n"
            "  - Validate and allowlist target URLs/hosts\n"
            "  - Block requests to internal IP ranges (10.x, 127.x, 169.254.x)\n"
            "  - Use a proxy with URL filtering"
        )

    def check(self, source: str, filename: str = "") -> list[Finding]:
        """Check for SSRF patterns."""
        findings: list[Finding] = []

        # URL built with fmt.Sprintf
        for lineno, line, _match in self._search_lines(source, URL_SPRINTF_PATTERN):
            findings.append(self._make_finding(
                lineno, line,
                "HTTP request URL built with fmt.Sprintf — SSRF risk.",
                Severity.CRITICAL,
            ))

        # URL built with concatenation
        for lineno, line, _match in self._search_lines(source, URL_CONCAT_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "HTTP request URL built with string concatenation — SSRF risk.",
                Severity.CRITICAL,
            ))

        # http.Get/Post with variable (not string literal)
        for lineno, line, _match in self._search_lines(source, HTTP_GET_PATTERN):
            if any(f.line == lineno for f in findings):
                continue
            # Skip if URL is a string literal
            after_paren = line[line.index("(") + 1:].strip() if "(" in line else ""
            if after_paren.startswith('"') or after_paren.startswith('`'):
                continue
            findings.append(self._make_finding(
                lineno, line,
                "http.Get/Post with variable URL — potential SSRF.",
                self.severity,
            ))

        # net.Dial with variable address
        for lineno, line, _match in self._search_lines(source, NET_DIAL_PATTERN):
            # net.Dial has two args: (network, address)
            # Check if the second argument is a literal
            if "(" in line:
                args_str = line[line.index("(") + 1:]
                # Split on first comma to get second arg
                parts = args_str.split(",", 1)
                if len(parts) >= 2:
                    second_arg = parts[1].strip()
                    if second_arg.startswith('"') or second_arg.startswith('`'):
                        continue
                elif parts[0].strip().startswith('"') or parts[0].strip().startswith('`'):
                    # Single arg literal — skip
                    continue
            findings.append(Finding(
                rule_id=self.rule_id,
                severity=Severity.MEDIUM,
                title="SSRF (net.Dial)",
                description=(
                    "net.Dial with variable address — potential SSRF."
                ),
                file="",
                line=lineno,
                code_snippet=line.strip()[:80],
                recommendation=(
                    "Validate the dial target against an allowlist."
                ),
                cwe=self.cwe,
            ))

        return findings

    def _make_finding(
        self, lineno: int, line: str, desc: str, severity: Severity
    ) -> Finding:
        """Create an SSRF finding."""
        return Finding(
            rule_id=self.rule_id,
            severity=severity,
            title="SSRF",
            description=desc,
            file="",
            line=lineno,
            code_snippet=line.strip()[:80],
            recommendation=(
                "Validate and allowlist target URLs. Block internal "
                "IP ranges (10.x, 127.x, 169.254.x)."
            ),
            cwe=self.cwe,
        )
