"""Auto-discovery system for OpenAI-compatible providers."""

import importlib
import inspect
from pathlib import Path
from typing import Dict, List, Optional

from loguru import logger

from .base import BaseLLMProvider, normalize_provider
from .openai_base import OpenAICompatibleProvider
from ..llm_registry import register_llm


class ProviderInfo:
    """Information about a discovered provider."""

    def __init__(self, provider_class):
        self.provider_class = provider_class
        self.provider_key = getattr(
            provider_class,
            "provider_key",
            provider_class.__name__.replace("Provider", "").upper(),
        )
        self.provider_name = provider_class.provider_name
        self.company_name = getattr(
            provider_class, "company_name", provider_class.provider_name
        )
        self.is_cloud = getattr(provider_class, "is_cloud", True)
        self.requires_auth_for_models = (
            provider_class.requires_auth_for_models()
        )

        # Generate display name from attributes
        self.display_name = self._generate_display_name()

    def _generate_display_name(self):
        """Generate a descriptive display name from provider attributes."""
        # Start with the provider name
        name_parts = [self.provider_name]

        # Add cloud/local indicator
        if self.is_cloud is True:
            name_parts.append("☁️ Cloud")
        elif self.is_cloud is False:
            name_parts.append("💻 Local")

        return " ".join(name_parts)

    def to_dict(self):
        """Convert to dictionary for API responses."""
        return {
            "value": self.provider_key,
            "label": self.display_name,
            "is_cloud": self.is_cloud,
        }


class ProviderDiscovery:
    """Discovers and manages OpenAI-compatible providers."""

    _instance = None
    _providers: Dict[str, ProviderInfo] = {}
    _discovered: bool = False

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._discovered = False
        return cls._instance

    def discover_providers(
        self, force_refresh: bool = False
    ) -> Dict[str, ProviderInfo]:
        """Discover all providers in the providers directory.

        Args:
            force_refresh: Force re-discovery even if already done

        Returns:
            Dictionary mapping provider keys to ProviderInfo objects
        """
        if self._discovered and not force_refresh:
            return self._providers

        self._providers.clear()
        # Scan the implementations subdirectory for providers
        implementations_dir = Path(__file__).parent / "implementations"

        if not implementations_dir.exists():
            logger.warning(
                f"Implementations directory not found: {implementations_dir}"
            )
            return self._providers

        # Scan all Python files in the implementations directory
        logger.info(f"Scanning directory: {implementations_dir}")
        for file_path in implementations_dir.glob("*.py"):
            # Skip special files (like __init__.py)
            if file_path.name.startswith("_"):
                continue

            module_name = file_path.stem
            logger.debug(f"Processing module: {module_name} from {file_path}")
            try:
                # Import the module from implementations subdirectory
                module = importlib.import_module(
                    f".implementations.{module_name}",
                    package="local_deep_research.llm.providers",
                )

                # Find all Provider classes (both OpenAICompatibleProvider and standalone)
                logger.debug(
                    f"Inspecting module {module_name} for Provider classes"
                )
                for name, obj in inspect.getmembers(module, inspect.isclass):
                    if inspect.isclass(obj):
                        logger.debug(
                            f"  Found class: {name}, bases: {obj.__bases__}"
                        )
                    # Check if it's a Provider class with a real provider_name.
                    # BaseLLMProvider now sets a default "unknown" so all
                    # subclasses inherit the attribute; skip ones that
                    # haven't customized it (incomplete fixture classes etc.).
                    if (
                        name.endswith("Provider")
                        and getattr(obj, "provider_name", "unknown")
                        not in (None, "", "unknown")
                        and issubclass(obj, BaseLLMProvider)
                        and obj is not OpenAICompatibleProvider
                        and obj is not BaseLLMProvider
                        # Only register classes DEFINED in this module, not ones
                        # merely imported into its namespace (e.g.
                        # custom_anthropic_endpoint imports AnthropicProvider as
                        # its base). Without this, the imported base would be
                        # re-registered from every module that imports it.
                        and obj.__module__ == module.__name__
                    ):
                        # Found a provider class
                        provider_info = ProviderInfo(obj)
                        self._providers[provider_info.provider_key] = (
                            provider_info
                        )

                        # Auto-register the provider directly using the class
                        register_llm(
                            normalize_provider(provider_info.provider_key),
                            obj.create_llm,
                        )
                        logger.info(
                            f"Auto-registered provider: {provider_info.provider_key}"
                        )

                        logger.info(
                            f"Discovered provider: {provider_info.provider_key} from {module_name}.py"
                        )

            except Exception:
                logger.exception(f"Error loading provider from {module_name}")

        self._discovered = True
        logger.info(f"Discovered {len(self._providers)} providers")
        return self._providers

    def get_provider_info(self, provider_key: str) -> Optional[ProviderInfo]:
        """Get information about a specific provider.

        Args:
            provider_key: The provider key (e.g., 'IONOS', 'GOOGLE')

        Returns:
            ProviderInfo object or None if not found
        """
        if not self._discovered:
            self.discover_providers()
        return self._providers.get(provider_key.upper())

    def get_provider_options(self) -> List[Dict]:
        """Get list of provider options for UI dropdowns.

        Returns:
            List of dictionaries with 'value' and 'label' keys
        """
        if not self._discovered:
            self.discover_providers()

        options = []
        for provider_info in self._providers.values():
            options.append(provider_info.to_dict())

        # Sort by label
        options.sort(key=lambda x: x["label"])
        return options

    def get_available_provider_options(
        self, settings_snapshot=None
    ) -> List[Dict]:
        """Get list of available provider options, filtered by availability.

        Filters out providers that are not available (e.g., missing API keys).
        Useful for contexts where only usable providers should be shown
        (e.g., starting a research). For settings/configuration UIs, prefer
        get_provider_options() so users can discover and configure new providers.

        Args:
            settings_snapshot: Settings snapshot for checking provider availability.
                Should be provided to correctly check cloud provider API keys.

        Returns:
            List of dictionaries with 'value' and 'label' keys
        """
        if not self._discovered:
            self.discover_providers()

        options = []
        for provider_info in self._providers.values():
            if not provider_info.provider_class.is_available(
                settings_snapshot=settings_snapshot
            ):
                logger.debug(
                    f"Provider {provider_info.provider_key} filtered out "
                    f"(not available)"
                )
                continue
            options.append(provider_info.to_dict())

        if not options:
            logger.warning(
                "No auto-discovered providers passed availability filter. "
                "Check that API keys are configured for cloud providers."
            )

        # Sort by label
        options.sort(key=lambda x: x["label"])
        return options

    def get_provider_class(self, provider_key: str):
        """Get the provider class for a given key.

        Args:
            provider_key: The provider key (e.g., 'IONOS', 'GOOGLE')

        Returns:
            Provider class or None if not found
        """
        provider_info = self.get_provider_info(provider_key)
        return provider_info.provider_class if provider_info else None


# Global instance
provider_discovery = ProviderDiscovery()


def discover_providers(force_refresh: bool = False) -> Dict[str, ProviderInfo]:
    """Discover all available providers.

    Args:
        force_refresh: Force re-discovery even if already done

    Returns:
        Dictionary mapping provider keys to ProviderInfo objects
    """
    return provider_discovery.discover_providers(force_refresh)


def get_discovered_provider_options() -> List[Dict]:
    """Get list of discovered provider options for UI dropdowns.

    Returns:
        List of dictionaries with 'value' and 'label' keys
    """
    return provider_discovery.get_provider_options()


def get_available_discovered_provider_options(
    settings_snapshot=None,
) -> List[Dict]:
    """Get list of available provider options, filtered by availability.

    Only returns providers that pass is_available() check. Useful for
    contexts where only usable providers matter (e.g., starting a research).
    For settings/configuration UIs, use get_discovered_provider_options()
    instead so users can discover and configure new providers.

    Args:
        settings_snapshot: Settings snapshot for checking provider availability

    Returns:
        List of dictionaries with 'value' and 'label' keys
    """
    return provider_discovery.get_available_provider_options(settings_snapshot)


def get_provider_class(provider_key: str):
    """Get the provider class for a given key.

    Args:
        provider_key: The provider key (e.g., 'IONOS', 'GOOGLE')

    Returns:
        Provider class or None if not found
    """
    return provider_discovery.get_provider_class(provider_key)
