"""Authentication and authorization dependencies.

A request is authenticated by either a JWT access token (``Authorization:
Bearer``) or an API key (``X-API-Key``). Both resolve to a :class:`Principal`
bound to exactly one workspace; :func:`require` then enforces a permission.
"""

from __future__ import annotations

import uuid
from collections.abc import Callable
from dataclasses import dataclass, field

from fastapi import Depends, HTTPException, status
from fastapi.security import APIKeyHeader, HTTPAuthorizationCredentials, HTTPBearer
from sqlalchemy.orm import Session

from legate.db.models.user import User
from legate.db.session import get_db
from legate.policy.rbac import Permission, role_has_permission, scopes_have_permission
from legate.policy.roles import Role
from legate.repositories import api_keys as api_keys_repo
from legate.repositories import users as users_repo
from legate.repositories import workspaces as workspaces_repo
from legate.security.api_keys import hash_api_key
from legate.security.tokens import TokenError, decode_token

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

_UNAUTH = HTTPException(
    status_code=status.HTTP_401_UNAUTHORIZED,
    detail="not authenticated",
    headers={"WWW-Authenticate": "Bearer"},
)


@dataclass
class Principal:
    """The authenticated caller, always bound to a single workspace."""

    auth_type: str  # "jwt" | "api_key"
    workspace_id: uuid.UUID
    user: User | None = None
    role: Role | None = None
    scopes: list[str] = field(default_factory=list)
    api_key_id: uuid.UUID | None = None

    @property
    def actor(self) -> str:
        if self.auth_type == "api_key":
            return f"apikey:{self.api_key_id}"
        return f"user:{self.user.id}" if self.user else "system"

    def has_permission(self, permission: Permission) -> bool:
        if self.auth_type == "api_key":
            return scopes_have_permission(self.scopes, permission)
        return self.role is not None and role_has_permission(self.role, permission)


def get_current_user(
    creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
    db: Session = Depends(get_db),
) -> User:
    """Resolve the user from a JWT access token (workspace not required)."""
    if creds is None:
        raise _UNAUTH
    try:
        payload = decode_token(creds.credentials, expected_type="access")
    except TokenError as exc:
        raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc

    user = users_repo.get_by_id(db, payload.user_id)
    if user is None or not user.is_active:
        raise _UNAUTH
    return user


def get_principal(
    creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
    api_key: str | None = Depends(_api_key_header),
    db: Session = Depends(get_db),
) -> Principal:
    """Resolve a workspace-bound principal from an API key or a JWT access token."""
    if api_key:
        key = api_keys_repo.get_active_by_hash(db, hash_api_key(api_key))
        if key is None:
            raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="invalid API key")
        api_keys_repo.touch_last_used(db, key)
        db.commit()
        return Principal(
            auth_type="api_key",
            workspace_id=key.workspace_id,
            scopes=list(key.scopes or []),
            api_key_id=key.id,
        )

    if creds is not None:
        try:
            payload = decode_token(creds.credentials, expected_type="access")
        except TokenError as exc:
            raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
        if payload.workspace_id is None:
            raise HTTPException(
                status.HTTP_400_BAD_REQUEST,
                detail="no active workspace; create or switch to one first",
            )
        user = users_repo.get_by_id(db, payload.user_id)
        if user is None or not user.is_active:
            raise _UNAUTH
        membership = workspaces_repo.get_membership(
            db, user_id=user.id, workspace_id=payload.workspace_id
        )
        if membership is None:
            raise HTTPException(status.HTTP_403_FORBIDDEN, detail="not a member of this workspace")
        return Principal(
            auth_type="jwt",
            workspace_id=payload.workspace_id,
            user=user,
            role=membership.role,
        )

    raise _UNAUTH


def require(permission: Permission) -> Callable[..., Principal]:
    """Build a dependency that enforces ``permission`` on the principal."""

    def dependency(principal: Principal = Depends(get_principal)) -> Principal:
        if not principal.has_permission(permission):
            raise HTTPException(
                status.HTTP_403_FORBIDDEN,
                detail=f"missing permission: {permission.value}",
            )
        return principal

    return dependency
