"""SSRF-001: Server-Side Request Forgery detection rule."""

from __future__ import annotations

import ast

from . import Rule
from ..models import Finding, Severity

HTTP_FUNCS = {
    "requests.get", "requests.post", "requests.put", "requests.delete",
    "requests.patch", "requests.head", "requests.request",
    "urllib.request.urlopen",
    "httpx.get", "httpx.post", "httpx.put", "httpx.delete",
    "httpx.patch", "httpx.head", "httpx.request",
}

SESSION_METHODS = {"get", "post", "put", "delete", "patch", "head", "request"}


class SSRFRule(Rule):
    """Detects HTTP requests with dynamic URLs (potential SSRF)."""

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

    @property
    def name(self) -> str:
        return "Server-Side Request Forgery"

    @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 dynamic URL may allow SSRF attacks."

    @property
    def detailed_description(self) -> str:
        return (
            "HTTP request with dynamic URL may allow SSRF attacks.\n\n"
            "Server-Side Request Forgery (SSRF) occurs when an application makes\n"
            "HTTP requests to URLs controlled by user input. An attacker can use\n"
            "this to access internal services, cloud metadata endpoints, or\n"
            "other resources not intended to be reachable.\n\n"
            "Examples of vulnerable code:\n"
            "  requests.get(user_url)\n"
            "  httpx.post(f'http://{host}/api')\n"
            "  urllib.request.urlopen(url_from_input)\n\n"
            "How to fix:\n"
            "  - Validate URLs against an allowlist of permitted hosts/schemes\n"
            "  - Block requests to private/internal IP ranges (10.x, 172.16.x, 169.254.x)\n"
            "  - Use a dedicated HTTP proxy that enforces URL policies"
        )

    def check(self, tree: ast.Module, source: str) -> list[Finding]:
        findings: list[Finding] = []
        for node in ast.walk(tree):
            if not isinstance(node, ast.Call):
                continue
            func_name = self._get_func_name(node)
            is_http = func_name in HTTP_FUNCS
            is_session = (
                isinstance(node.func, ast.Attribute)
                and node.func.attr in SESSION_METHODS
                and isinstance(node.func.value, ast.Name)
                and "session" in node.func.value.id.lower()
            )
            if not (is_http or is_session):
                continue
            if not node.args:
                continue
            url_arg = node.args[0]
            if isinstance(url_arg, (ast.Name, ast.JoinedStr, ast.BinOp)):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="Potential SSRF",
                    description=f"{func_name}() with dynamic URL argument.",
                    file="",
                    line=node.lineno,
                    code_snippet=self._get_snippet(source, node.lineno),
                    recommendation="Validate URLs against an allowlist of permitted hosts/schemes.",
                    cwe="CWE-918",
                ))
        return findings
