"""Core auth dependency facade.

Routers depend on this module instead of a concrete access-provider package.
The active auth module provides the request principal through the platform
capability registry.
"""

from __future__ import annotations

from dataclasses import dataclass

from fastapi import Depends, Header, HTTPException, Request, status
from sqlalchemy.orm import Session

from govoplan_core.core.access import (
    CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER,
    ApiPrincipalProvider,
    PermissionEvaluator,
    PrincipalRef,
)
from govoplan_core.core.registry import PlatformRegistry
from govoplan_core.db.session import get_session
from govoplan_core.security.module_permissions import scopes_grant_compatible


@dataclass(slots=True)
class ApiPrincipal:
    """Request principal exposed to API routers.

    The stable fields live in ``principal``. ``account``, ``user``,
    ``api_key``, and ``auth_session`` are provider-owned objects kept for
    current routers that still need concrete account/session state.
    """

    principal: PrincipalRef
    account: object
    user: object
    api_key: object | None = None
    auth_session: object | None = None
    permission_evaluator: PermissionEvaluator | None = None

    @property
    def account_id(self) -> str:
        return self.principal.account_id

    @property
    def membership_id(self) -> str | None:
        return self.principal.membership_id

    @property
    def tenant_id(self) -> str:
        if self.principal.tenant_id is None:
            raise RuntimeError("Tenant principal has no active tenant id.")
        return self.principal.tenant_id

    @property
    def scopes(self) -> frozenset[str]:
        return self.principal.scopes

    @property
    def group_ids(self) -> frozenset[str]:
        return self.principal.group_ids

    @property
    def role_ids(self) -> frozenset[str]:
        return self.principal.role_ids

    @property
    def function_assignment_ids(self) -> frozenset[str]:
        return self.principal.function_assignment_ids

    @property
    def delegation_ids(self) -> frozenset[str]:
        return self.principal.delegation_ids

    @property
    def identity_id(self) -> str | None:
        return self.principal.identity_id

    @property
    def acting_for_account_id(self) -> str | None:
        return self.principal.acting_for_account_id

    @property
    def acting_assignment_id(self) -> str | None:
        return self.principal.acting_assignment_id

    @property
    def auth_method(self) -> str:
        return self.principal.auth_method

    @property
    def api_key_id(self) -> str | None:
        return self.principal.api_key_id

    @property
    def session_id(self) -> str | None:
        return self.principal.session_id

    @property
    def email(self) -> str | None:
        return self.principal.email

    @property
    def display_name(self) -> str | None:
        return self.principal.display_name

    def has(self, required_scope: str) -> bool:
        if self.permission_evaluator is not None:
            return self.permission_evaluator.has_scope(self.principal, required_scope)
        return scopes_grant_compatible(self.scopes, required_scope)

    def to_platform_principal(self) -> PrincipalRef:
        return self.principal


def _registry_from_request(request: Request) -> PlatformRegistry | None:
    registry = getattr(request.app.state, "govoplan_registry", None)
    return registry if isinstance(registry, PlatformRegistry) else None


def _api_principal_provider_from_request(request: Request) -> ApiPrincipalProvider:
    registry = _registry_from_request(request)
    if registry is None or not registry.has_capability(CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER):
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Auth provider is not available")
    capability = registry.require_capability(CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER)
    if not isinstance(capability, ApiPrincipalProvider):
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Invalid auth provider capability")
    return capability


def get_api_principal(
    request: Request,
    session: Session = Depends(get_session),
    authorization: str | None = Header(default=None),
    x_api_key: str | None = Header(default=None, alias="X-API-Key"),
) -> ApiPrincipal:
    principal = _api_principal_provider_from_request(request).resolve_api_principal(
        request,
        session,
        authorization=authorization,
        x_api_key=x_api_key,
    )
    if not isinstance(principal, ApiPrincipal):
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Invalid API principal")
    return principal


def has_scope(principal: ApiPrincipal, required_scope: str) -> bool:
    return principal.has(required_scope)


def require_scope(required_scope: str):
    def dependency(principal: ApiPrincipal = Depends(get_api_principal)) -> ApiPrincipal:
        if not has_scope(principal, required_scope):
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Missing scope: {required_scope}")
        return principal

    return dependency


def require_any_scope(*required_scopes: str):
    def dependency(principal: ApiPrincipal = Depends(get_api_principal)) -> ApiPrincipal:
        if not any(has_scope(principal, required) for required in required_scopes):
            joined = ", ".join(required_scopes)
            raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Requires one of: {joined}")
        return principal

    return dependency


__all__ = [
    "ApiPrincipal",
    "get_api_principal",
    "has_scope",
    "require_any_scope",
    "require_scope",
]
