"""Provider-based abstraction for learning model backends.

Each model domain (forecasting, inventory, analytics) has a provider interface.
Providers are pluggable — swap between built-in math, statsforecast, stockpyl,
or custom implementations without changing calling code.

The registry pattern lets you:
    1. Register multiple backends for the same domain
    2. Pick one by name at runtime
    3. Fall back to built-in if a library isn't installed

Usage:
    from platoon.learning.providers import get_provider

    # Get the best available forecasting provider
    forecaster = get_provider("forecasting")
    result = forecaster.forecast(series, horizon=14)

    # Explicitly pick a backend
    forecaster = get_provider("forecasting", backend="statsforecast")
    result = forecaster.forecast(series, horizon=14)
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional, Type


# ---------------------------------------------------------------------------
# Base provider
# ---------------------------------------------------------------------------


class ModelProvider(ABC):
    """Base class for all learning model providers.

    Subclasses implement domain-specific methods (forecast, optimize, segment).
    Each provider wraps a specific backend library.
    """

    name: str = "base"
    domain: str = "base"  # "forecasting" | "inventory" | "analytics"
    backend: str = "builtin"  # which library backs this provider

    @classmethod
    def is_available(cls) -> bool:
        """Check if the backing library is installed."""
        return True

    def describe(self) -> Dict[str, Any]:
        """Return metadata about this provider for agent discovery."""
        return {
            "name": self.name,
            "domain": self.domain,
            "backend": self.backend,
            "available": self.is_available(),
        }


# ---------------------------------------------------------------------------
# Registry
# ---------------------------------------------------------------------------

_REGISTRY: Dict[str, List[Type[ModelProvider]]] = {}


def register_provider(cls: Type[ModelProvider]) -> Type[ModelProvider]:
    """Decorator to register a model provider class."""
    domain = cls.domain
    if domain not in _REGISTRY:
        _REGISTRY[domain] = []
    _REGISTRY[domain].append(cls)
    return cls


def get_provider(domain: str, backend: Optional[str] = None) -> ModelProvider:
    """Get a model provider instance for the given domain.

    If backend is specified, returns that specific one.
    Otherwise returns the best available (library-backed preferred over builtin).

    Args:
        domain: "forecasting" | "inventory" | "analytics"
        backend: Optional specific backend name (e.g. "statsforecast", "stockpyl")

    Returns:
        Instantiated ModelProvider

    Raises:
        ValueError: If no provider found for the domain/backend
    """
    candidates = _REGISTRY.get(domain, [])
    if not candidates:
        raise ValueError(f"No providers registered for domain '{domain}'")

    if backend:
        for cls in candidates:
            if cls.backend == backend:
                if not cls.is_available():
                    raise ImportError(
                        f"Backend '{backend}' for domain '{domain}' requires "
                        f"a library that is not installed."
                    )
                return cls()
        raise ValueError(f"No provider with backend '{backend}' for domain '{domain}'")

    # Auto-select: prefer library-backed over builtin, check availability
    available = [cls for cls in candidates if cls.is_available()]
    if not available:
        raise ImportError(f"No available providers for domain '{domain}'")

    # Sort: library-backed first, builtin last
    available.sort(key=lambda c: (c.backend == "builtin", c.name))
    return available[0]()


def list_providers(domain: Optional[str] = None) -> List[Dict[str, Any]]:
    """List all registered providers, optionally filtered by domain."""
    results = []
    for d, providers in _REGISTRY.items():
        if domain and d != domain:
            continue
        for cls in providers:
            results.append({
                "name": cls.name,
                "domain": d,
                "backend": cls.backend,
                "available": cls.is_available(),
            })
    return results
