"""AP013: Vulnerable Components Detection."""

from collections.abc import Iterator

from apiposture.core.models.endpoint import Endpoint
from apiposture.core.models.enums import Severity
from apiposture.core.models.finding import Finding

from apiposture_pro.rules.base import ProSecurityRule


class AP013VulnerableComponents(ProSecurityRule):
    """
    AP013: Vulnerable Components Detection.

    Detects usage of components with known security issues:
    - Endpoints using deprecated/insecure frameworks features
    - Common vulnerable patterns in endpoint definitions
    - Weak TLS/SSL configurations
    """

    def __init__(self) -> None:
        super().__init__()
        self._reported_issues: set[str] = set()

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

    @property
    def name(self) -> str:
        return "Vulnerable Components"

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

    @property
    def description(self) -> str:
        return (
            "Detects usage of vulnerable or deprecated framework features "
            "and components (OWASP A06:2021)"
        )

    def evaluate(self, endpoint: Endpoint) -> Iterator[Finding]:
        """Evaluate endpoint for vulnerable component usage."""
        # Skip test/example/docs files — not production endpoints
        file_parts = endpoint.file_path.parts
        if any(
            part in ("tests", "test", "examples", "example", "docs", "testing")
            or part.startswith("test_")
            for part in file_parts
        ):
            return

        func_name = endpoint.function_name.lower()
        route_lower = endpoint.route.lower()

        # Check for deprecated authentication methods
        deprecated_auth = ["basic_auth", "md5", "sha1"]
        uses_deprecated_auth = any(
            deprecated in func_name or deprecated in route_lower
            for deprecated in deprecated_auth
        )

        if uses_deprecated_auth:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' appears to use deprecated "
                    "authentication methods"
                ),
                recommendation=(
                    "Avoid MD5, SHA1, and Basic Auth. Use bcrypt, Argon2, or PBKDF2 "
                    "for password hashing. Use token-based auth (JWT, OAuth2) instead "
                    "of Basic Auth for API endpoints."
                ),
                severity=Severity.HIGH,
            )

        # Check for XML processing (XXE vulnerabilities)
        xml_keywords = ["xml", "xmlrpc", "soap"]
        processes_xml = any(keyword in func_name or keyword in route_lower for keyword in xml_keywords)

        if processes_xml:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' processes XML data "
                    "which may be vulnerable to XXE attacks"
                ),
                recommendation=(
                    "Disable XML external entities (XXE). For lxml: "
                    "use parser=etree.XMLParser(resolve_entities=False). "
                    "For defusedxml: use defusedxml.ElementTree instead of xml.etree. "
                    "Consider using JSON instead of XML for new APIs."
                ),
                severity=Severity.HIGH,
            )

        # Check for deserialization endpoints (unsafe pickle)
        # Removed "loads" — json.loads is safe and ubiquitous
        deserialize_keywords = ["pickle", "deserialize", "unmarshal", "yaml_load"]
        uses_deserialization = any(keyword in func_name for keyword in deserialize_keywords)

        if uses_deserialization:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' may perform unsafe deserialization"
                ),
                recommendation=(
                    "Never deserialize untrusted data with pickle. "
                    "Use JSON for data exchange. If complex objects are needed, "
                    "use JSON + schema validation (Pydantic, marshmallow) instead."
                ),
                severity=Severity.CRITICAL,
            )

        # Check for JSONP endpoints (CSRF and XSS risks)
        if "jsonp" in func_name or "jsonp" in route_lower:
            yield self.create_finding(
                endpoint,
                message=(
                    f"JSONP endpoint '{endpoint.full_route}' is vulnerable to CSRF and XSS"
                ),
                recommendation=(
                    "JSONP is deprecated and insecure. Use CORS headers instead. "
                    "Configure Access-Control-Allow-Origin properly for cross-origin requests."
                ),
                severity=Severity.HIGH,
            )

        # Check for insecure random number usage
        # Only flag explicit randomness function names, not "token" which is
        # expected in auth endpoints (login returns tokens, verify-token, etc.)
        random_keywords = ["random", "generate_secret", "nonce", "generate_key"]
        generates_random = any(keyword in func_name for keyword in random_keywords)

        if generates_random and endpoint.is_write_endpoint:
            issue_key = f"random_{endpoint.route}"
            if issue_key not in self._reported_issues:
                self._reported_issues.add(issue_key)
                yield self.create_finding(
                    endpoint,
                    message=(
                        f"Endpoint '{endpoint.full_route}' generates random values "
                        "for security purposes"
                    ),
                    recommendation=(
                        "Use secrets module for cryptographic randomness, not random module. "
                        "For tokens: use secrets.token_urlsafe(). "
                        "For keys: use secrets.token_bytes(). "
                        "The random module is not cryptographically secure."
                    ),
                    severity=Severity.MEDIUM,
                )

        # Check for direct regex usage (ReDoS vulnerabilities)
        # Removed "match", "search", "validate", "pattern" — too generic
        regex_keywords = ["regex", "re_compile"]
        uses_regex = any(keyword in func_name for keyword in regex_keywords)

        if uses_regex:
            issue_key = f"regex_{endpoint.route}"
            if issue_key not in self._reported_issues:
                self._reported_issues.add(issue_key)
                yield self.create_finding(
                    endpoint,
                    message=(
                        f"Endpoint '{endpoint.full_route}' uses regex validation "
                        "which may be vulnerable to ReDoS"
                    ),
                    recommendation=(
                        "Be careful with complex regex patterns. Avoid nested quantifiers "
                        "like (a+)+, (a*)*. Use timeout for regex operations. "
                        "Consider using specialized validators instead of regex for "
                        "common patterns (email, URL, etc.)."
                    ),
                    severity=Severity.LOW,
                )

        # Weak TLS/SSL detection
        ssl_keywords = ["ssl", "tls", "certificate"]
        has_ssl_keyword = any(keyword in func_name for keyword in ssl_keywords)
        weak_ssl_hints = ["disable", "verify_false", "no_verify", "insecure", "skip_verify"]
        has_weak_hint = any(hint in func_name for hint in weak_ssl_hints)

        if has_ssl_keyword and has_weak_hint:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' appears to disable SSL/TLS verification"
                ),
                recommendation=(
                    "Never disable SSL/TLS certificate verification in production. "
                    "Use proper certificates from trusted CAs. "
                    "For development, use self-signed certificates instead of disabling verification."
                ),
                severity=Severity.CRITICAL,
            )
