"""AP009: Broken Authentication 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 AP009BrokenAuthentication(ProSecurityRule):
    """
    AP009: Broken Authentication Detection.

    Detects authentication-related security issues including:
    - Write endpoints without authentication
    - Authentication endpoints with weak configuration
    - Endpoints handling credentials without proper protection
    """

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

    @property
    def name(self) -> str:
        return "Broken Authentication"

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

    @property
    def description(self) -> str:
        return (
            "Detects broken authentication patterns including write operations "
            "without authentication, weak authentication configuration, and "
            "credential handling vulnerabilities (OWASP A07:2021)"
        )

    def evaluate(self, endpoint: Endpoint) -> Iterator[Finding]:
        """Evaluate endpoint for broken authentication issues."""
        # Check for write operations without authentication
        if endpoint.is_write_endpoint and endpoint.authorization.is_public:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Write endpoint '{endpoint.full_route}' accepts {endpoint.display_methods} "
                    "without authentication"
                ),
                recommendation=(
                    "Require authentication for write operations. "
                    "Add authentication decorators/dependencies (e.g., Depends(get_current_user), "
                    "@login_required, IsAuthenticated permission class)"
                ),
            )

        # Check for authentication endpoints with suspicious patterns
        auth_keywords = ["login", "signin", "auth", "authenticate", "token", "session"]
        route_lower = endpoint.route.lower()
        func_lower = endpoint.function_name.lower()

        is_auth_endpoint = any(
            keyword in route_lower or keyword in func_lower for keyword in auth_keywords
        )

        if is_auth_endpoint:
            # Authentication endpoint should be POST, not GET
            if 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. "
                        "Credentials should never be sent via GET parameters."
                    ),
                    severity=Severity.HIGH,
                )

            # Check for credential-related endpoints that are public
            credential_keywords = ["password", "credential", "secret"]
            is_credential_endpoint = any(
                keyword in route_lower or keyword in func_lower
                for keyword in credential_keywords
            )

            if is_credential_endpoint and endpoint.authorization.is_public:
                # Password reset endpoints should be public, but others should not
                if "reset" not in route_lower and "forgot" not in route_lower:
                    yield self.create_finding(
                        endpoint,
                        message=(
                            f"Credential-handling endpoint '{endpoint.full_route}' "
                            "is publicly accessible"
                        ),
                        recommendation=(
                            "Credential-handling endpoints should require authentication "
                            "unless specifically designed for password reset flows."
                        ),
                        severity=Severity.HIGH,
                    )

        # Check for session/token management without auth
        session_keywords = ["session", "logout", "signout", "revoke"]
        is_session_endpoint = any(
            keyword in route_lower or keyword in func_lower for keyword in session_keywords
        )

        if is_session_endpoint and endpoint.authorization.is_public:
            yield self.create_finding(
                endpoint,
                message=(
                    f"Session management endpoint '{endpoint.full_route}' "
                    "is publicly accessible"
                ),
                recommendation=(
                    "Session management endpoints (logout, token revocation) "
                    "should require authentication to prevent session fixation attacks."
                ),
                severity=Severity.MEDIUM,
            )
