from typing import AsyncGenerator, Callable, Any
from fastapi import HTTPException, Request, Security
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from nilauth_credit_middleware.credit_locking_client_singleton import (
    CreditClientSingleton,
)
from nilauth_credit_middleware.credit_locking_client import CreditLockingClient
from nilauth_credit_middleware.api_model import AuthConfig, ValidateCredentialResponse
import logging
import pydantic
logger = logging.getLogger(__name__)

# Bearer token scheme
bearer_scheme = HTTPBearer()


class AuthenticationInfo(pydantic.BaseModel):
    user_id: str
    credential: str
    is_public: bool
    is_valid: bool


class AuthContext:
    """Context object yielded by auth dependency."""

    def __init__(self, request: Request, user_id: str, credential: str):
        self.request = request
        self._user_id = user_id
        self._credential = credential

    @property
    def user_id(self) -> str:
        """Read-only property for user_id"""
        return self._user_id

    @property
    def credential(self) -> str:
        """Read-only property for credential"""
        return self._credential


class AuthDependency:
    """
    FastAPI dependency for authenticating API endpoints using yield pattern.

    Example:
        # Using bearer token (most common)
        auth = AuthDependency(
            config=AuthConfig(
                credential_extractor=bearer_credential_extractor,
                public_identifiers=False
            )
        )

        # Custom extractor from header
        async def header_extractor(request: Request) -> str:
            return request.headers.get("X-API-Key")

        auth = AuthDependency(
            config=AuthConfig(
                credential_extractor=header_extractor,
                public_identifiers=True
            )
        )

        @app.post("/process")
        async def process_data(
            data: dict,
            auth: AuthContext = Depends(auth)
        ):
            return {"user_id": auth.user_id, "result": "processed"}
    """

    def __init__(self, config: AuthConfig):
        self.config = config

    async def __call__(self, request: Request) -> AsyncGenerator[AuthContext, None]:
        """Validate credential, yield context."""
        credit_client = self._get_credit_client()
        user_credential = await self._extract_credential(request)

        # Validate the credential
        response = await credit_client.validate_credential(
            user_credential,
            self.config.public_identifiers
        )

        if not response.is_valid:
            raise HTTPException(status_code=401, detail="Invalid authentication")

        auth = AuthContext(request, response.user_id, user_credential)
        try:
            yield auth
        except Exception:
            raise

    def _get_credit_client(self) -> CreditLockingClient:
        """Get the credit client singleton."""
        try:
            return CreditClientSingleton.get_client()
        except RuntimeError as e:
            logger.error(f"Credit client not initialized: {e}")
            raise HTTPException(status_code=500, detail="Credit service unavailable")

    async def _extract_credential(self, request: Request) -> str:
        """Extract and validate credential from request."""
        try:
            credential = await self.config.credential_extractor(request)
            if not credential:
                raise HTTPException(status_code=401, detail="Credential not found")
            return credential
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"Credential extraction failed: {e}")
            raise HTTPException(status_code=401, detail="Invalid authentication")


# Convenience credential extractors

async def bearer_credential_extractor(request: Request) -> str:
    """
    Extract credential from Authorization Bearer token.

    This is a convenience extractor that uses the bearer_scheme.
    Use this with AuthDependency for standard bearer token authentication.

    Example:
        auth = AuthDependency(
            config=AuthConfig(
                credential_extractor=bearer_credential_extractor,
                public_identifiers=False
            )
        )
    """
    auth_header = request.headers.get("Authorization")
    if not auth_header:
        raise HTTPException(status_code=401, detail="Missing Authorization header")

    if not auth_header.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid Authorization header format")

    return auth_header[7:]  # Remove "Bearer " prefix


class HTTPBearerAuth:
    """
    HTTP Bearer token authentication dependency for FastAPI.

    This class validates bearer tokens using the nilauth-credit service.
    It follows the same pattern as FastAPI's security utilities (HTTPBearer, HTTPBasic, etc.).

    Args:
        is_public: Whether to validate as a public credential (default: False)

    Returns:
        AuthenticationInfo with user_id, credential, is_public, and is_valid

    Raises:
        HTTPException: If credentials are invalid or service is unavailable

    Example:
        from fastapi import FastAPI, Depends
        from nilauth_credit_middleware import HTTPBearerAuth, AuthenticationInfo

        app = FastAPI()

        # Private credentials (default)
        auth = HTTPBearerAuth()

        @app.get("/protected")
        async def protected_endpoint(
            auth_info: AuthenticationInfo = Depends(auth)
        ):
            return {"user_id": auth_info.user_id}

        # Public credentials
        public_auth = HTTPBearerAuth(is_public=True)

        @app.get("/public-protected")
        async def public_endpoint(
            auth_info: AuthenticationInfo = Depends(public_auth)
        ):
            return {"user_id": auth_info.user_id}
    """

    def __init__(self, is_public: bool = False):
        """
        Initialize the HTTP Bearer authentication handler.

        Args:
            is_public: If True, validates as a public credential
        """
        self.is_public = is_public

    async def __call__(
        self,
        credentials: HTTPAuthorizationCredentials = Security(bearer_scheme),
    ) -> AuthenticationInfo:
        """
        Validate the bearer token credentials.

        Args:
            credentials: HTTP Bearer token credentials from request

        Returns:
            AuthenticationInfo with validated user information

        Raises:
            HTTPException: If credentials are invalid or service is unavailable
        """
        try:
            credit_client = CreditClientSingleton.get_client()
        except RuntimeError as e:
            logger.error(f"Credit client not initialized: {e}")
            raise HTTPException(status_code=500, detail="Credit service unavailable")

        credential = credentials.credentials

        try:
            response: ValidateCredentialResponse = await credit_client.validate_credential(
                credential=credential,
                is_public=self.is_public,
            )

            if not response.is_valid:
                raise HTTPException(status_code=401, detail="Invalid credentials")

            return AuthenticationInfo(
                user_id=response.user_id,
                credential=credential,
                is_public=response.is_public,
                is_valid=response.is_valid,
            )
        except HTTPException:
            raise
        except Exception as e:
            logger.error(f"Credential validation failed: {e}")
            raise HTTPException(status_code=401, detail="Authentication failed")


def create_auth_dependency(
    credential_extractor: Callable[[Request], Any],
    public_identifiers: bool = False,
) -> AuthDependency:
    """
    Factory function to create an AuthDependency instance.

    Args:
        credential_extractor: Async function that extracts credential from request
        public_identifiers: If True, validates as public credential (default: False)

    Returns:
        AuthDependency instance ready to use with Depends()

    Example:
        # Bearer token authentication
        from nilauth_credit_middleware import bearer_credential_extractor

        auth = create_auth_dependency(
            credential_extractor=bearer_credential_extractor,
            public_identifiers=False
        )

        # Custom header authentication
        async def get_api_key(request: Request) -> str:
            return request.headers.get("X-API-Key")

        auth = create_auth_dependency(
            credential_extractor=get_api_key,
            public_identifiers=True
        )

        @app.post("/process")
        async def endpoint(
            data: dict,
            auth: AuthContext = Depends(auth)
        ):
            return {"user_id": auth.user_id}
    """
    config = AuthConfig(
        credential_extractor=credential_extractor,
        public_identifiers=public_identifiers,
    )
    return AuthDependency(config)
