"""AP015: API Key Exposure Detection."""

from collections.abc import Iterator

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

from apiposture_pro.rules.base import ProSecurityRule


class AP015APIKeyExposure(ProSecurityRule):
    """
    AP015: API Key Exposure Detection.

    Detects API key exposure issues:
    - API keys in URL query parameters (GET requests)
    - Authentication tokens in URL paths
    - Credentials passed via insecure methods
    """

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

    @property
    def name(self) -> str:
        return "API Key Exposure"

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

    @property
    def description(self) -> str:
        return (
            "Detects API key and credential exposure through insecure methods "
            "like URL parameters and GET requests"
        )

    def evaluate(self, endpoint: Endpoint) -> Iterator[Finding]:
        """Check for API key exposure in endpoint design."""
        route = endpoint.route.lower()
        func_name = endpoint.function_name.lower()

        # Check for API key/token in route parameters
        key_params = [
            "api_key",
            "apikey",
            "api-key",
            "token",
            "access_token",
            "auth_token",
            "key",
            "secret",
            "password",
            "credential",
        ]

        has_key_param = any(
            f"{{{param}}}" in route or f"<{param}>" in route for param in key_params
        )

        if has_key_param:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' accepts API key or token as URL parameter"
                ),
                recommendation=(
                    "Never pass API keys or tokens in URL parameters. Use HTTP headers instead: "
                    "Authorization: Bearer <token> or X-API-Key: <key>. "
                    "URL parameters are logged in web server logs, browser history, and "
                    "can be exposed in Referer headers."
                ),
                severity=Severity.CRITICAL,
            )

        # Check for authentication via GET method
        auth_keywords = ["auth", "login", "signin", "token", "credential"]
        is_auth_endpoint = any(keyword in route or keyword in func_name for keyword in auth_keywords)

        if is_auth_endpoint and HttpMethod.GET in endpoint.methods:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Authentication endpoint '{endpoint.full_route}' accepts GET requests"
                ),
                recommendation=(
                    "Authentication endpoints should only accept POST requests. "
                    "GET requests expose credentials in URLs, which are logged and cached. "
                    "Use POST with credentials in the request body."
                ),
                severity=Severity.HIGH,
            )

        # Check for password/secret in function name with GET
        secret_keywords = ["password", "secret", "credential", "key"]
        handles_secrets = any(keyword in func_name for keyword in secret_keywords)

        if handles_secrets and HttpMethod.GET in endpoint.methods:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Endpoint '{endpoint.full_route}' handles secrets/credentials via GET"
                ),
                recommendation=(
                    "Use POST or PUT methods for operations involving secrets. "
                    "GET request data is visible in URLs and logs. "
                    "Sensitive data should be sent in request body with proper encryption."
                ),
                severity=Severity.HIGH,
            )

        # Check for API key validation endpoints
        validate_keywords = ["validate", "verify", "check"]
        validates_key = any(keyword in func_name for keyword in validate_keywords)

        if validates_key and any(keyword in func_name for keyword in ["key", "token", "credential"]):
            if endpoint.authorization.is_public:
                yield self.create_finding(
                    endpoint,
                    message=(
                        f"API key validation endpoint '{endpoint.full_route}' is publicly accessible"
                    ),
                    recommendation=(
                        "API key validation endpoints should require authentication. "
                        "Public validation endpoints can be abused for key enumeration attacks."
                    ),
                    severity=Severity.MEDIUM,
                )

        # Check for callback URLs with tokens
        if "callback" in route or "callback" in func_name:
            if has_key_param or any(word in route for word in ["token", "key", "code"]):
                yield self.create_finding(
                    endpoint,
                    message=(
                        f"OAuth/callback endpoint '{endpoint.full_route}' "
                        "may expose tokens in URL"
                    ),
                    recommendation=(
                        "For OAuth callbacks, use authorization code flow instead of implicit flow. "
                        "Validate redirect URIs and use short-lived codes. "
                        "Consider using PKCE for additional security."
                    ),
                    severity=Severity.MEDIUM,
                )
