"""
AuthManager - Public API for authentication and authorization.

Provides FastAPI-compatible dependency decorators for protecting endpoints
with JWT verification and role-based access control.
"""

from typing import Any, Dict, List, Optional, Union

from fastapi import Depends, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer

from .config.auth_config import AuthConfig
from .ports.auth_port import AuthPort
from .adapters.clerk_adapter import ClerkAdapter
from .exceptions.auth_exceptions import (
    AuthManagerError,
    ConfigurationError,
    TokenError,
    AuthorizationError,
)


_security = HTTPBearer()


class AuthManager:
    """
    Public API for authentication and authorization.

    Provides FastAPI dependency functions that can be used with Depends()
    to protect endpoints with JWT verification and role-based access control.

    Example:
        auth = AuthManager({"jwks_url": os.getenv("NEURONOS_AUTH_JWKS_URL")})

        @app.get("/api/protected")
        def protected(user: dict = Depends(auth.require_auth())):
            return {"user_id": user["sub"]}

        @app.get("/api/admin")
        def admin_only(user: dict = Depends(auth.require_roles(["org:admin"]))):
            return {"message": "Welcome admin"}

        @app.get("/api/tenant")
        def tenant_only(user: dict = Depends(auth.require_org("org_2abc123"))):
            return {"message": "Org access granted"}

        @app.get("/api/coordinator")
        def coordinator(user: dict = Depends(auth.require_org_role("org_2abc123", ["org:coordinator"]))):
            return {"message": "Org coordinator access"}
    """

    _ADAPTERS = {
        "clerk": ClerkAdapter,
    }

    def __init__(self, config: Optional[Union[AuthConfig, Dict[str, Any]]] = None):
        """
        Initialize AuthManager with configuration.

        Args:
            config: AuthConfig object or dictionary with auth parameters.
                    Sensitive values fall back to environment variables.

        Raises:
            ConfigurationError: If configuration is invalid or missing
        """
        if config is None:
            # Attempt to build config entirely from env variables
            config = AuthConfig()
        elif isinstance(config, dict):
            try:
                config = AuthConfig(**config)
            except Exception as e:
                raise ConfigurationError(f"Invalid configuration: {str(e)}")

        self.config = config
        self._adapter: AuthPort = self._create_adapter(config)

    def _create_adapter(self, config: AuthConfig) -> AuthPort:
        adapter_cls = self._ADAPTERS.get(config.provider)
        if not adapter_cls:
            raise ConfigurationError(
                f"Unsupported provider: {config.provider}. "
                f"Supported: {', '.join(self._ADAPTERS.keys())}"
            )
        return adapter_cls(config)

    # ===== FastAPI Dependency Factories =====

    def require_auth(self):
        """
        FastAPI dependency that verifies the JWT and returns the decoded payload.

        Returns 401 if token is missing or invalid.

        Usage:
            @app.get("/protected")
            def endpoint(user: dict = Depends(auth.require_auth())):
                ...
        """
        adapter = self._adapter

        async def _dependency(
            credentials: HTTPAuthorizationCredentials = Depends(_security),
        ) -> Dict[str, Any]:
            try:
                return await adapter.verify_token_async(credentials.credentials)
            except TokenError as e:
                raise HTTPException(status_code=401, detail=str(e))

        return _dependency

    def require_roles(self, roles: List[str]):
        """
        FastAPI dependency that verifies JWT and checks the user has one of the given roles.

        Returns 401 if token is invalid, 403 if role is not matched.

        Args:
            roles: List of acceptable roles (e.g., ["coordinador", "admin"])

        Usage:
            @app.get("/admin")
            def endpoint(user: dict = Depends(auth.require_roles(["admin"]))):
                ...
        """
        adapter = self._adapter

        async def _dependency(
            credentials: HTTPAuthorizationCredentials = Depends(_security),
        ) -> Dict[str, Any]:
            try:
                payload = await adapter.verify_token_async(credentials.credentials)
            except TokenError as e:
                raise HTTPException(status_code=401, detail=str(e))

            if not adapter.has_role(payload, roles):
                raise HTTPException(
                    status_code=403,
                    detail=f"Requires one of roles: {', '.join(roles)}",
                )
            return payload

        return _dependency

    def require_org(self, org_id: str):
        """
        FastAPI dependency that verifies JWT and checks the user belongs to a specific org.

        Returns 401 if token is invalid, 403 if org doesn't match.

        Args:
            org_id: Required organization ID (e.g., "org_2abc123")

        Usage:
            @app.get("/tenant")
            def endpoint(user: dict = Depends(auth.require_org("org_2abc123"))):
                ...
        """
        adapter = self._adapter

        async def _dependency(
            credentials: HTTPAuthorizationCredentials = Depends(_security),
        ) -> Dict[str, Any]:
            try:
                payload = await adapter.verify_token_async(credentials.credentials)
            except TokenError as e:
                raise HTTPException(status_code=401, detail=str(e))

            if adapter.get_org_id(payload) != org_id:
                raise HTTPException(
                    status_code=403,
                    detail=f"Access restricted to organization '{org_id}'.",
                )
            return payload

        return _dependency

    def require_org_role(self, org_id: str, roles: List[str]):
        """
        FastAPI dependency that verifies JWT, org membership, AND role in one step.

        Returns 401 if token is invalid, 403 if org or role doesn't match.

        Args:
            org_id: Required organization ID
            roles: List of acceptable roles within that org

        Usage:
            @app.get("/org-coordinator")
            def endpoint(user: dict = Depends(auth.require_org_role("org_2abc123", ["org:coordinator"]))):
                ...
        """
        adapter = self._adapter

        async def _dependency(
            credentials: HTTPAuthorizationCredentials = Depends(_security),
        ) -> Dict[str, Any]:
            try:
                payload = await adapter.verify_token_async(credentials.credentials)
            except TokenError as e:
                raise HTTPException(status_code=401, detail=str(e))

            if adapter.get_org_id(payload) != org_id:
                raise HTTPException(
                    status_code=403,
                    detail=f"Access restricted to organization '{org_id}'.",
                )

            if not adapter.has_role(payload, roles):
                raise HTTPException(
                    status_code=403,
                    detail=f"Requires one of roles: {', '.join(roles)}",
                )
            return payload

        return _dependency

    # ===== Direct API (non-decorator usage) =====

    def verify_token(self, token: str) -> Dict[str, Any]:
        """Verify and decode a JWT token (synchronous)."""
        return self._adapter.verify_token(token)

    async def verify_token_async(self, token: str) -> Dict[str, Any]:
        """Verify and decode a JWT token (asynchronous)."""
        return await self._adapter.verify_token_async(token)

    def get_user_id(self, payload: Dict[str, Any]) -> Optional[str]:
        """Extract user ID from decoded payload."""
        return self._adapter.get_user_id(payload)

    def get_org_id(self, payload: Dict[str, Any]) -> Optional[str]:
        """Extract organization ID from decoded payload."""
        return self._adapter.get_org_id(payload)
