"""
Authentication module for FSAL agent.

Provides bearer token authentication using environment variable FSAL_TOKEN.
Optional Gatekeeper integration when available (for aperion-legendary-ai-main).

ASYMMETRIC BEHAVIOR (Constitution B):
- Server: Logs warning if FSAL_TOKEN not set, but allows startup (dev mode).
         If token IS set, enforces strict 401/403 responses.
- Client/Scripts: Should fail immediately if tokens are missing (not handled here).

This asymmetry allows development flexibility while ensuring production security.

GATEKEEPER INTEGRATION:
When running as part of aperion-legendary-ai-main, set USE_GATEKEEPER=1 to enable
integration with The Gatekeeper for unified auth. This adds:
- Principal/Role/Permission support
- Audit logging
- JWT token validation
"""

from __future__ import annotations

import hmac
import logging
import os
from typing import TYPE_CHECKING, Any

from fastapi import Depends, Header, HTTPException, Request

logger = logging.getLogger(__name__)

# Track if we've logged the no-auth warning (avoid spam)
_no_auth_warning_logged = False
_gatekeeper_mode_logged = False


# Feature flag for Gatekeeper integration - checked at runtime via function
def _use_gatekeeper() -> bool:
    """Check if Gatekeeper integration is enabled (runtime check)."""
    return os.environ.get("USE_GATEKEEPER", "0") == "1"


# Gatekeeper types (only used for type checking)
if TYPE_CHECKING:
    from typing import Protocol

    class Principal(Protocol):
        """Protocol for Gatekeeper Principal."""

        user_id: str


def _try_import_gatekeeper():
    """
    Attempt to import Gatekeeper components.

    Returns None if Gatekeeper is not available (standalone mode).
    Tries pip package 'gatekeeper' first, then falls back to monolith path.
    """
    if not _use_gatekeeper():
        return None

    try:
        from gatekeeper import (
            Principal,
            Role,
            get_auditor,
            get_token_service,
        )

        return {
            "Principal": Principal,
            "Role": Role,
            "get_auditor": get_auditor,
            "get_token_service": get_token_service,
        }
    except ImportError:
        pass

    # Fallback: try monolith path (until gatekeeper is extracted as pip package)
    try:
        from stack.aperion.gatekeeper import (  # type: ignore[import-not-found]
            Principal,
            Role,
            get_auditor,
            get_token_service,
        )

        return {
            "Principal": Principal,
            "Role": Role,
            "get_auditor": get_auditor,
            "get_token_service": get_token_service,
        }
    except ImportError:
        logger.debug("[FSAL Auth] Gatekeeper not available - using standalone mode")
        return None


def _import_permission():
    """Import Permission enum from gatekeeper (with monolith fallback)."""
    try:
        from gatekeeper import Permission

        return Permission
    except ImportError:
        pass
    try:
        from stack.aperion.gatekeeper import Permission  # type: ignore[import-not-found]

        return Permission
    except ImportError:
        return None


def _legacy_require_auth(authorization: str | None = Header(None)) -> None:
    """
    Legacy bearer token authentication.

    Used when Gatekeeper is not available or USE_GATEKEEPER=0.
    """
    global _no_auth_warning_logged

    fsal_token = os.environ.get("FSAL_TOKEN")

    # No token configured — enforce in production, warn in dev
    if not fsal_token:
        aperion_env = os.environ.get("APERION_ENV", "development").lower()
        if aperion_env == "production":
            raise HTTPException(
                status_code=503,
                detail="FSAL_TOKEN not configured. Refusing to run without auth in production.",
            )
        if not _no_auth_warning_logged:
            logger.warning(
                "[FSAL Auth] FSAL_TOKEN not set - running in NO-AUTH mode. "
                "This is INSECURE and should only be used for development!"
            )
            _no_auth_warning_logged = True
        return

    # Token configured - enforce strict validation
    if not authorization:
        raise HTTPException(
            status_code=401,
            detail="Authorization required. Provide Bearer token in Authorization header.",
        )

    if not authorization.startswith("Bearer "):
        raise HTTPException(
            status_code=401,
            detail="Invalid authorization format. Expected 'Bearer <token>'.",
        )

    token = authorization.split(" ", 1)[1] if " " in authorization else ""
    if not token:
        raise HTTPException(status_code=401, detail="Bearer token is empty.")

    # Use constant-time comparison to prevent timing attacks
    if not hmac.compare_digest(token.encode("utf-8"), fsal_token.encode("utf-8")):
        raise HTTPException(status_code=403, detail="Invalid authentication token. Access denied.")


def _gatekeeper_require_auth(
    request: Request = None,
    authorization: str | None = Header(None),
) -> Any:
    """
    Gatekeeper-based authentication for FSAL.

    Uses The Gatekeeper's TokenService for unified auth across all services.
    Falls back to legacy token check for backwards compatibility.
    Returns the authenticated Principal for downstream permission checks.
    """
    global _gatekeeper_mode_logged, _no_auth_warning_logged

    gk = _try_import_gatekeeper()
    if gk is None:
        # Gatekeeper not available - fall back to legacy
        _legacy_require_auth(authorization)
        return None

    if not _gatekeeper_mode_logged:
        logger.info("[FSAL Auth] Using Gatekeeper for authentication")
        _gatekeeper_mode_logged = True

    fsal_token = os.environ.get("FSAL_TOKEN")

    # No token configured — enforce in production, warn in dev
    if not fsal_token:
        aperion_env = os.environ.get("APERION_ENV", "development").lower()
        if aperion_env == "production":
            raise HTTPException(
                status_code=503,
                detail="FSAL_TOKEN not configured. Refusing to run without auth in production.",
            )
        if not _no_auth_warning_logged:
            logger.warning(
                "[FSAL Auth] FSAL_TOKEN not set - running in NO-AUTH mode. "
                "This is INSECURE and should only be used for development!"
            )
            _no_auth_warning_logged = True
        # Return a dev principal with full access
        return gk["Principal"](
            user_id="dev-user",
            roles=[gk["Role"].ADMIN],
            metadata={"dev_mode": True},
        )

    if not authorization:
        raise HTTPException(
            status_code=401,
            detail="Authorization required. Provide Bearer token in Authorization header.",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # Get request context if available
    method = request.method if request else "GET"
    path = str(request.url.path) if request else "/"
    ip_address = request.client.host if request and request.client else None
    correlation_id = request.headers.get("X-Correlation-ID") if request else None

    # Try Gatekeeper first
    token_service = gk["get_token_service"]()
    result = token_service.authenticate(authorization, method=method, path=path)

    # Audit the attempt
    if request:
        auditor = gk["get_auditor"]()
        auditor.log_auth_attempt(
            result,
            method=method,
            path=path,
            ip_address=ip_address,
            correlation_id=correlation_id,
        )

    if result.success:
        if request:
            request.state.principal = result.principal
        return result.principal

    # Gatekeeper failed - try legacy token as fallback
    if authorization.startswith("Bearer "):
        token = authorization.split(" ", 1)[1]
        if hmac.compare_digest(token.encode("utf-8"), fsal_token.encode("utf-8")):
            # Legacy token valid - return admin principal
            principal = gk["Principal"](
                user_id="fsal-legacy-token",
                roles=[gk["Role"].ADMIN],
                metadata={"legacy_auth": True},
            )
            if request:
                request.state.principal = principal
            return principal

    # Both Gatekeeper and legacy failed
    raise HTTPException(
        status_code=403,
        detail="Invalid authentication token. Access denied.",
    )


# Dynamic auth selector - checks USE_GATEKEEPER at runtime
def require_auth(
    request: Request = None,
    authorization: str | None = Header(None),
) -> Any:
    """
    Dynamic authentication dependency.

    Checks USE_GATEKEEPER env var at runtime and dispatches to the
    appropriate auth handler. This allows changing auth mode without
    module reload.
    """
    if _use_gatekeeper():
        return _gatekeeper_require_auth(request, authorization)
    else:
        return _legacy_require_auth(authorization)


def require_auth_direct(authorization: str | None) -> None:
    """
    Direct auth function for unit testing and non-FastAPI contexts.

    Unlike require_auth, this accepts a plain string, not a FastAPI Header.
    """
    return _legacy_require_auth(authorization)


def get_auth_status() -> dict[str, Any]:
    """
    Get current authentication configuration status.

    Returns:
        Dict with auth_enabled, mode, security_level, and gatekeeper info
    """
    fsal_token = os.environ.get("FSAL_TOKEN")
    return {
        "auth_enabled": bool(fsal_token),
        "mode": "strict" if fsal_token else "none",
        "security_level": "production" if fsal_token else "development-only",
        "gatekeeper_enabled": _use_gatekeeper(),
        "gatekeeper_available": _try_import_gatekeeper() is not None,
    }


def reset_auth_warning() -> None:
    """Reset the auth warning flags (for testing)."""
    global _no_auth_warning_logged, _gatekeeper_mode_logged
    _no_auth_warning_logged = False
    _gatekeeper_mode_logged = False


# Permission-based auth dependencies (require Gatekeeper)


def require_read(
    request: Request,
    principal: Any = Depends(require_auth),
) -> Any:
    """Require read permission for FSAL operations."""
    if not _use_gatekeeper() or principal is None:
        return principal

    gk = _try_import_gatekeeper()
    if gk is None:
        return principal

    try:
        Permission = _import_permission()
        if Permission is None:
            raise ImportError("Permission not available")

        if not principal.has_permission(Permission.READ):
            raise HTTPException(status_code=403, detail="Read permission required")
    except (ImportError, AttributeError):
        pass  # Gatekeeper permissions not available
    return principal


def require_write(
    request: Request,
    principal: Any = Depends(require_auth),
) -> Any:
    """Require write permission for FSAL operations."""
    if not _use_gatekeeper() or principal is None:
        return principal

    gk = _try_import_gatekeeper()
    if gk is None:
        return principal

    try:
        Permission = _import_permission()
        if Permission is None:
            raise ImportError("Permission not available")

        if not principal.has_permission(Permission.WRITE):
            raise HTTPException(status_code=403, detail="Write permission required")
    except (ImportError, AttributeError):
        pass
    return principal


def require_delete(
    request: Request,
    principal: Any = Depends(require_auth),
) -> Any:
    """Require delete permission for FSAL operations."""
    if not _use_gatekeeper() or principal is None:
        return principal

    gk = _try_import_gatekeeper()
    if gk is None:
        return principal

    try:
        Permission = _import_permission()
        if Permission is None:
            raise ImportError("Permission not available")

        if not principal.has_permission(Permission.DELETE):
            raise HTTPException(status_code=403, detail="Delete permission required")
    except (ImportError, AttributeError):
        pass
    return principal
