"""Module-level registry for DashboardAuthProvider instances.

Plugins call ``register_provider`` via the plugin context hook at startup.
The auth gate middleware iterates ``list_providers()`` and uses
``get_provider`` to dispatch on the session's ``provider`` field.
"""
from __future__ import annotations

import logging
import threading
from typing import List, Optional

from hermes_constants import hermes_home_key
from hermes_cli.dashboard_auth.base import (
    DashboardAuthProvider,
    assert_protocol_compliance,
)

_log = logging.getLogger(__name__)
_lock = threading.Lock()
_providers: dict[str, DashboardAuthProvider] = {}
_scoped_providers: dict[str, dict[str, DashboardAuthProvider]] = {}


def _merged(scope: Optional[str] = None) -> dict[str, DashboardAuthProvider]:
    providers = dict(_providers)
    providers.update(_scoped_providers.get(scope or hermes_home_key(), {}))
    return providers


def register_provider(
    provider: DashboardAuthProvider,
    *,
    scope: Optional[str] = None,
) -> None:
    """Register a provider.

    Raises:
        TypeError: on protocol violation.
        ValueError: if a provider with the same name is already registered.
    """
    assert_protocol_compliance(type(provider))
    with _lock:
        target = _providers if scope is None else _scoped_providers.setdefault(scope, {})
        effective = target if scope is None else _merged(scope)
        if provider.name in effective:
            raise ValueError(
                f"dashboard-auth provider already registered: {provider.name!r}"
            )
        target[provider.name] = provider
    _log.info(
        "dashboard-auth: registered provider %r (%s)",
        provider.name, provider.display_name,
    )


def get_provider(
    name: str,
    *,
    scope: Optional[str] = None,
) -> Optional[DashboardAuthProvider]:
    """Return the registered provider for ``name``, or None if unknown."""
    with _lock:
        return _merged(scope).get(name)


def snapshot_registration(
    name: str,
    *,
    scope: Optional[str] = None,
) -> Optional[DashboardAuthProvider]:
    with _lock:
        target = _providers if scope is None else _scoped_providers.get(scope, {})
        return target.get(name)


def restore_registration(
    name: str,
    current: DashboardAuthProvider,
    previous: Optional[DashboardAuthProvider],
    *,
    scope: Optional[str] = None,
) -> bool:
    """Restore a host-owned provider registration if it is still current."""
    with _lock:
        target = _providers if scope is None else _scoped_providers.setdefault(scope, {})
        if target.get(name) is not current:
            return False
        if previous is None:
            target.pop(name, None)
        else:
            target[name] = previous
        if scope is not None and not target:
            _scoped_providers.pop(scope, None)
    return True


def list_providers(*, scope: Optional[str] = None) -> List[DashboardAuthProvider]:
    """All registered providers, in registration order."""
    with _lock:
        return list(_merged(scope).values())


def list_token_providers() -> List[DashboardAuthProvider]:
    """Registered providers that support non-interactive token auth.

    The subset of ``list_providers()`` whose ``supports_token`` flag is True,
    in registration order. The ``token_auth`` middleware seam consults these
    (and only these) when a token-authable route is hit, so OAuth/password-only
    providers are never asked to ``verify_token``. Returns an empty list when
    no token provider is registered — a token-authable route then fails
    closed (401), never open.
    """
    return [p for p in list_providers() if getattr(p, "supports_token", False)]


def list_session_providers() -> List[DashboardAuthProvider]:
    """Registered providers with supports_session True (interactive cookie
    sessions). The login page, /auth/login, and the gate's verify/refresh loops
    consult only these. Mirror of list_token_providers.
    """
    return [p for p in list_providers() if getattr(p, "supports_session", True)]


def clear_providers() -> None:
    """Test-only: drop all registrations."""
    with _lock:
        _providers.clear()
        _scoped_providers.clear()
