import logging

from fastapi import HTTPException

from paskia.fastapi.logging import log_permission_denied
from paskia.util import permutil, sessionutil

logger = logging.getLogger(__name__)


class AuthException(HTTPException):
    """Exception raised during authentication/authorization with metadata for the UI.

    Attributes:
        status_code: HTTP status code (401 for auth, 403 for authz)
        detail: Error message
        mode: UI mode ('login' or 'reauth')
        clear_session: Whether to clear the session cookie (True for invalid sessions)
        metadata: Additional data to pass to the frontend
    """

    def __init__(
        self,
        status_code: int,
        detail: str,
        mode: str,
        clear_session: bool = False,
        **metadata,
    ):
        super().__init__(status_code=status_code, detail=detail)
        self.mode = mode
        self.clear_session = clear_session
        self.metadata = metadata


async def auth_error_content(exc: AuthException) -> dict:
    """Generate JSON response content for an AuthException.

    Returns a dict with detail, mode, and iframe URL for src embedding.
    """
    # Build hash fragment from mode and metadata
    params = {"mode": exc.mode, **exc.metadata}
    fragment = "&".join(f"{k}={v}" for k, v in params.items() if v is not None)
    iframe_url = f"/auth/restricted/iframe#{fragment}"
    return {
        "detail": exc.detail,
        "auth": {
            "mode": exc.mode,
            "iframe": iframe_url,
            **exc.metadata,
        },
    }


async def verify(
    auth: str | None,
    perm: list[str],
    match=permutil.has_all,
    host: str | None = None,
    max_age: str | None = None,
):
    """Validate session token and optional list of required permissions.

    Returns the session context.

    Raises AuthException on failure with metadata for UI rendering.
    """
    if not auth:
        raise AuthException(
            status_code=401,
            detail="Authentication required",
            mode="login",
        )

    ctx = await permutil.session_context(auth, host)
    if not ctx:
        raise AuthException(
            status_code=401,
            detail="Your session has expired. Please sign in again.",
            mode="login",
            clear_session=True,
        )
    # User's theme preference for iframe (only if explicitly set)
    user_theme = ctx.user.theme if ctx.user.theme else None

    # Check max_age requirement if specified
    if max_age:
        try:
            if not sessionutil.check_session_age(ctx, max_age):
                raise AuthException(
                    status_code=401,
                    detail="Additional authentication required",
                    mode="reauth",
                    theme=user_theme,
                )
        except ValueError as e:
            # Invalid max_age format - log but don't fail the request
            logger.warning(f"Invalid max_age format '{max_age}': {e}")

    if not match(ctx, perm):
        effective_scopes = (
            {p.scope for p in (ctx.permissions or [])}
            if ctx.permissions
            else set(ctx.role.permissions or [])
        )
        missing = sorted(set(perm) - effective_scopes)
        log_permission_denied(
            ctx, perm, missing, require_all=(match == permutil.has_all)
        )
        raise AuthException(
            status_code=403,
            mode="forbidden",
            detail="Permission required",
            theme=user_theme,
        )

    return ctx
