"""RES-001: Unbounded Resource Consumption detection rule."""

from __future__ import annotations

import ast

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

SUBPROCESS_FUNCS = {
    "subprocess.run", "subprocess.call", "subprocess.check_output",
    "subprocess.check_call", "subprocess.Popen",
}

REQUEST_FUNCS = {
    "requests.get", "requests.post", "requests.put", "requests.delete",
    "requests.patch", "requests.head", "requests.request",
    "httpx.get", "httpx.post", "httpx.put", "httpx.delete",
}


class ResourceExhaustionRule(Rule):
    """Detects unbounded reads, requests without timeout, and subprocess without timeout."""

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

    @property
    def name(self) -> str:
        return "Unbounded Resource Consumption"

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

    @property
    def description(self) -> str:
        return "Missing size limits or timeouts may cause resource exhaustion."

    def _has_keyword(self, node: ast.Call, name: str) -> bool:
        return any(kw.arg == name for kw in node.keywords)

    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)
            if (isinstance(node.func, ast.Attribute)
                    and node.func.attr == "read"
                    and not node.args
                    and not self._has_keyword(node, "size")):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="Unbounded file read",
                    description="file.read() without size limit.",
                    file="",
                    line=node.lineno,
                    code_snippet=self._get_snippet(source, node.lineno),
                    recommendation="Always specify size limits for reads and timeouts for external calls.",
                    cwe="CWE-400",
                    confidence="MEDIUM",
                ))
            elif func_name in REQUEST_FUNCS and not self._has_keyword(node, "timeout"):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="HTTP request without timeout",
                    description=f"{func_name}() without timeout parameter.",
                    file="",
                    line=node.lineno,
                    code_snippet=self._get_snippet(source, node.lineno),
                    recommendation="Always specify size limits for reads and timeouts for external calls.",
                    cwe="CWE-400",
                ))
            elif func_name in SUBPROCESS_FUNCS and not self._has_keyword(node, "timeout"):
                findings.append(Finding(
                    rule_id=self.rule_id,
                    severity=self.severity,
                    title="Subprocess without timeout",
                    description=f"{func_name}() without timeout parameter.",
                    file="",
                    line=node.lineno,
                    code_snippet=self._get_snippet(source, node.lineno),
                    recommendation="Always specify size limits for reads and timeouts for external calls.",
                    cwe="CWE-400",
                ))
        return findings
