"""FastAPI middleware for credential extraction and validation.

This module provides middleware for FastAPI applications to:
- Extract and validate API key from Authorization header (Bearer token)
- Extract and validate MetaInfo JWT from X-META-INFO header

Note: This middleware only extracts and validates credentials. Authorization decisions
are left to route handlers.
"""

import logging
from pathlib import Path

from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint

from hezor_common.transfer.base_sdk.constants import ANONYMOUS_HEADER_PUBLIC_KEY
from hezor_common.transfer.middlewares.constants import (
    REQ_HEADER_APP_NAME_KEY,
    REQ_HEADER_META_INFO_KEY,
)
from hezor_common.transfer.middlewares.meta_info_decoder import decode_meta_info

logger = logging.getLogger(__name__)


class CredentialMiddleware(BaseHTTPMiddleware):
    """Credential extraction and validation middleware for FastAPI.

    This middleware extracts and validates credentials:
    - API key from Authorization header (Bearer token)
    - MetaInfo from X-META-INFO header (JWT token)

    The validated credentials are stored in request.state for authorization decisions
    in route handlers.

    Attributes
    ----------
    api_key : str | None
        Expected API key for validation. If None, API key validation is skipped.
    public_key_pem : str | bytes | None
        PEM-encoded public key for JWT verification. If None, uses anonymous public key.
    public_key_path : str | Path | None
        Path to public key file. Takes precedence over public_key_pem if provided.
    verify_jwt : bool
        Whether to verify JWT signature (default: True)

    Examples
    --------
    >>> from fastapi import FastAPI
    >>> app = FastAPI()
    >>> # Using public key content
    >>> app.add_middleware(
    ...     CredentialMiddleware,
    ...     api_key="your-api-key",
    ...     public_key_pem=public_key_content,
    ... )
    >>> # Using public key file path
    >>> app.add_middleware(
    ...     CredentialMiddleware,
    ...     api_key="your-api-key",
    ...     public_key_path="/path/to/public_key.pem",
    ... )
    """

    def __init__(
        self,
        app,
        api_key: str | None = None,
        public_key_pem: str | bytes | None = None,
        public_key_path: str | Path | None = None,
        verify_jwt: bool = True,
    ):
        """Initialize credential extraction middleware.

        Parameters
        ----------
        app
            FastAPI application instance
        api_key : str | None, optional
            Expected API key for validation (default: None, accepts any token)
        public_key_pem : str | bytes | None, optional
            PEM-encoded public key for JWT verification (default: None, uses anonymous key)
        public_key_path : str | Path | None, optional
            Path to public key file for JWT verification. Takes precedence over public_key_pem.
        verify_jwt : bool, optional
            Whether to verify JWT signature (default: True)
        """
        super().__init__(app)
        self.api_key = api_key

        # Load public key from file path if provided, otherwise use PEM content
        if public_key_path is not None:
            public_key_file = Path(public_key_path)
            if not public_key_file.exists():
                msg = f"Public key file not found: {public_key_path}"
                raise FileNotFoundError(msg)
            self.public_key_pem = public_key_file.read_bytes()
            logger.info(f"Loaded public key from {public_key_path}")
        else:
            self.public_key_pem = public_key_pem or ANONYMOUS_HEADER_PUBLIC_KEY
            if public_key_pem is None:
                logger.warning(
                    "No public key provided, using anonymous public key. "
                    "This is not secure for production use."
                )

        self.verify_jwt = verify_jwt

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        """Process request to extract and validate credentials.

        Extracts and validates:
        1. API key from Authorization header (Bearer token)
        2. MetaInfo from X-META-INFO header (JWT token)

        Stores results in request.state:
        - request.state.api_key: str | None
        - request.state.meta_info: MetaInfo | None
        - request.state.app_name: str | None
        - request.state.api_key_error: str | None (if API key validation fails)
        - request.state.meta_info_error: str | None (if MetaInfo validation fails)

        Parameters
        ----------
        request : Request
            Incoming HTTP request
        call_next : Callable
            Next middleware or route handler

        Returns
        -------
        Response
            HTTP response
        """
        # Initialize request state
        request.state.api_key = None
        request.state.meta_info = None
        request.state.app_name = None
        request.state.api_key_error = None
        request.state.meta_info_error = None

        # Extract and validate API key
        auth_header = request.headers.get("Authorization")
        if auth_header:
            if auth_header.startswith("Bearer "):
                token = auth_header[7:]
                if self.api_key is not None:
                    if token == self.api_key:
                        request.state.api_key = token
                        logger.debug(f"API key validated: {token[:6]}***")
                    else:
                        request.state.api_key_error = "Invalid API key"
                        logger.warning("API key validation failed")
                else:
                    # No API key configured, accept any token
                    request.state.api_key = token
                    logger.debug(f"API key accepted (no validation): {token[:6]}***")
            else:
                request.state.api_key_error = "Invalid Authorization header format"
                logger.warning("Invalid Authorization header format")

        # Extract app name
        app_name_header = request.headers.get(REQ_HEADER_APP_NAME_KEY)
        if app_name_header:
            request.state.app_name = app_name_header
            logger.debug(f"App name extracted: {app_name_header}")

        # Extract and validate MetaInfo JWT
        meta_header = request.headers.get(REQ_HEADER_META_INFO_KEY)
        if meta_header:
            try:
                meta_info = decode_meta_info(
                    meta_header,
                    self.public_key_pem,
                    verify=self.verify_jwt,
                )
                request.state.meta_info = meta_info
                logger.debug(
                    f"MetaInfo extracted: subject={meta_info.subject}, "
                    f"subject_code={meta_info.subject_code}"
                )

            except Exception as e:
                request.state.meta_info_error = f"Invalid MetaInfo JWT: {e!s}"
                logger.warning(f"MetaInfo validation failed: {e}")

        # Continue to next middleware/handler
        response = await call_next(request)
        return response
