"""Triple authentication: Nginx cookie header, Bearer token (JWT), API key."""

from __future__ import annotations

import hashlib
import logging
import os
import time
from collections import OrderedDict
from typing import Annotated

import httpx
import jwt
from fastapi import HTTPException, Request, Security
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer

logger = logging.getLogger(__name__)

_api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)
_bearer_scheme = HTTPBearer(auto_error=False)

# Header set by Nginx after validating the session cookie
_NGINX_AUTH_HEADER = "X-Ilum-Authenticated"

# ---------------------------------------------------------------------------
# Configuration (read once at import time, overridable via env)
# ---------------------------------------------------------------------------
_DEFAULT_CORE_URL = "http://ilum-core:9888"
_CORE_URL = os.environ.get("ILUM_CORE_URL", "") or _DEFAULT_CORE_URL
_JWT_PUBLIC_KEY = os.environ.get("ILUM_JWT_PUBLIC_KEY", "")
_JWT_ISSUER_URI = os.environ.get("ILUM_JWT_ISSUER_URI", "https://ilum.cloud")
_JWT_AUDIENCES = os.environ.get("ILUM_JWT_AUDIENCES", "")
_CACHE_TTL = int(os.environ.get("ILUM_BEARER_CACHE_TTL", "60"))

# ---------------------------------------------------------------------------
# In-memory cache for forwarded token validation results
# ---------------------------------------------------------------------------
_MAX_CACHE_SIZE = 1000


class _TokenCache:
    """LRU cache mapping token hashes to (valid, expiry_timestamp)."""

    def __init__(self, max_size: int = _MAX_CACHE_SIZE, ttl: int = _CACHE_TTL) -> None:
        self._store: OrderedDict[str, tuple[bool, float]] = OrderedDict()
        self._max_size = max_size
        self._ttl = ttl

    def get(self, token_hash: str) -> bool | None:
        entry = self._store.get(token_hash)
        if entry is None:
            return None
        valid, expires_at = entry
        if time.monotonic() > expires_at:
            self._store.pop(token_hash, None)
            return None
        # Move to end (most recently used)
        self._store.move_to_end(token_hash)
        return valid

    def put(self, token_hash: str, valid: bool) -> None:
        if token_hash in self._store:
            self._store.move_to_end(token_hash)
        self._store[token_hash] = (valid, time.monotonic() + self._ttl)
        while len(self._store) > self._max_size:
            self._store.popitem(last=False)


_token_cache = _TokenCache()


def _check_nginx_cookie(request: Request) -> bool:
    """Return True if Nginx validated the session cookie."""
    return request.headers.get(_NGINX_AUTH_HEADER, "").lower() == "true"


def _check_api_key(api_key: str | None) -> bool:
    """Return True if the API key matches the configured secret."""
    if not api_key:
        return False
    expected = os.environ.get("ILUM_API_KEY", "")
    if not expected:
        return False
    return api_key == expected


def _try_local_jwt(token: str) -> bool:
    """Validate JWT locally using the configured public key. Returns True if valid."""
    if not _JWT_PUBLIC_KEY:
        return False
    try:
        options: dict[str, bool] = {}
        kwargs: dict[str, object] = {
            "algorithms": ["RS256"],
            "options": options,
        }
        if _JWT_ISSUER_URI:
            kwargs["issuer"] = _JWT_ISSUER_URI
        if _JWT_AUDIENCES:
            kwargs["audience"] = [a.strip() for a in _JWT_AUDIENCES.split(",") if a.strip()]
        else:
            options["verify_aud"] = False
        jwt.decode(token, _JWT_PUBLIC_KEY, **kwargs)  # type: ignore[arg-type]
        return True
    except jwt.PyJWTError:
        return False


async def _try_forward_to_core(token: str) -> bool:
    """Forward Bearer token to ilum-core for validation. Returns True if valid."""
    token_hash = hashlib.sha256(token.encode()).hexdigest()

    cached = _token_cache.get(token_hash)
    if cached is not None:
        return cached

    core_url = os.environ.get("ILUM_CORE_URL", "") or _DEFAULT_CORE_URL
    url = f"{core_url.rstrip('/')}/authenticate/set-permission-cookies"
    try:
        async with httpx.AsyncClient(timeout=5.0) as client:
            resp = await client.post(
                url,
                headers={"Authorization": f"Bearer {token}"},
            )
        valid = resp.status_code == 200
    except (httpx.HTTPError, httpx.TimeoutException):
        valid = False

    _token_cache.put(token_hash, valid)
    return valid


async def _check_bearer(
    credentials: HTTPAuthorizationCredentials | None,
) -> bool:
    """Validate a Bearer token via local JWT or ilum-core forwarding."""
    if credentials is None:
        return False
    token = credentials.credentials

    # Path A: local JWT validation (if public key configured)
    if _JWT_PUBLIC_KEY and _try_local_jwt(token):
        return True

    # Path B: forward to ilum-core
    return await _try_forward_to_core(token)


async def require_auth(
    request: Request,
    api_key: Annotated[str | None, Security(_api_key_header)] = None,
    bearer: Annotated[HTTPAuthorizationCredentials | None, Security(_bearer_scheme)] = None,
) -> None:
    """FastAPI dependency that enforces authentication.

    Accepts any of:
    - ``X-Ilum-Authenticated: true`` header (set by Nginx after cookie check)
    - ``Authorization: Bearer <token>`` header (JWT validated locally or via ilum-core)
    - ``X-API-Key`` header matching ``ILUM_API_KEY`` environment variable

    Raises 401 if none passes.
    """
    if _check_nginx_cookie(request):
        return
    if await _check_bearer(bearer):
        return
    if _check_api_key(api_key):
        return
    raise HTTPException(
        status_code=401,
        detail=(
            "Authentication required. Provide Authorization: Bearer <token>, "
            "X-API-Key header, or access via Nginx proxy."
        ),
    )
