"""
BackendFactory – Factory Pattern for creating backend instances.

Loads backend classes from dotted-path strings defined in settings,
caches instances, and provides a registry lookup by channel name.
"""
from __future__ import annotations

import importlib
import logging
from typing import Any

from django_notify_hub.exceptions import BackendNotConfiguredError
from django_notify_hub.types import NotificationChannel

logger = logging.getLogger("django_notify_hub")

# Module-level registry: channel → backend instance
_registry: dict[str, "AbstractNotificationBackend"] = {}  # type: ignore[name-defined]
_initialized = False


def _import_backend(dotted_path: str) -> type:
    """Import a class from a dotted Python path string."""
    try:
        module_path, class_name = dotted_path.rsplit(".", 1)
        module = importlib.import_module(module_path)
        return getattr(module, class_name)
    except (ImportError, AttributeError, ValueError) as exc:
        raise BackendNotConfiguredError(dotted_path) from exc


def _build_registry() -> dict[str, Any]:
    """Instantiate all configured backends and index by channel value."""
    from django_notify_hub.conf import app_settings

    registry: dict[str, Any] = {}
    for dotted_path in app_settings.BACKENDS:
        try:
            cls = _import_backend(dotted_path)
            instance = cls()
            channel_key = instance.channel.value if hasattr(instance.channel, "value") else str(instance.channel)
            registry[channel_key] = instance
            logger.debug("BackendFactory: registered %s → %s", channel_key, cls.__name__)
        except Exception as exc:
            logger.error("BackendFactory: failed to load backend '%s': %s", dotted_path, exc)
    return registry


class BackendFactory:
    """
    Lazily loads and caches backend instances.

    Usage::

        backend = BackendFactory.get("fcm")
        backend.send(payload, user)
    """

    @classmethod
    def _ensure_initialized(cls) -> None:
        global _registry, _initialized
        if not _initialized:
            _registry = _build_registry()
            _initialized = True

    @classmethod
    def get(cls, channel: str | NotificationChannel) -> Any:
        """Return the backend instance for *channel*.  Raises if not configured."""
        cls._ensure_initialized()
        key = channel.value if isinstance(channel, NotificationChannel) else channel
        if key not in _registry:
            raise BackendNotConfiguredError(
                f"No backend registered for channel '{key}'. "
                f"Available: {list(_registry)}"
            )
        return _registry[key]

    @classmethod
    def all(cls) -> list[Any]:
        """Return all configured backend instances."""
        cls._ensure_initialized()
        return list(_registry.values())

    @classmethod
    def reset(cls) -> None:
        """Clear the registry (used in tests to reload settings)."""
        global _registry, _initialized
        _registry = {}
        _initialized = False

