"""
Coverage tests for local_deep_research/llm/providers/auto_discovery.py

Focuses on paths not covered by tests/llm_providers/test_auto_discovery.py:
- ProviderInfo.__init__ with and without optional class attributes
- ProviderInfo._generate_display_name() for cloud=True, cloud=False
- ProviderInfo.to_dict() shape
- ProviderDiscovery singleton behaviour
- ProviderDiscovery.get_provider_info() triggers discover_providers if not done
- ProviderDiscovery.get_provider_class() returns None for unknown key
- ProviderDiscovery.get_provider_options() sorts by label
- get_available_discovered_provider_options() filters unavailable providers
- Module-level convenience functions delegate correctly
"""

from unittest.mock import MagicMock


from local_deep_research.llm.providers.auto_discovery import (
    ProviderInfo,
    ProviderDiscovery,
    discover_providers,
    get_discovered_provider_options,
    get_available_discovered_provider_options,
    get_provider_class,
)


# ---------------------------------------------------------------------------
# Minimal provider class stubs
# ---------------------------------------------------------------------------


def _make_provider_class(
    provider_name="TestProv",
    provider_key="TESTPROV",
    company_name=None,
    is_cloud=True,
    requires_auth=True,
):
    cls = MagicMock()
    cls.__name__ = f"{provider_name}Provider"
    cls.provider_name = provider_name
    cls.provider_key = provider_key
    if company_name is not None:
        cls.company_name = company_name
    else:
        del cls.company_name  # force AttributeError → getattr default
    cls.is_cloud = is_cloud
    cls.requires_auth_for_models = MagicMock(return_value=requires_auth)
    cls.is_available = MagicMock(return_value=True)
    return cls


# ---------------------------------------------------------------------------
# ProviderInfo
# ---------------------------------------------------------------------------


class TestProviderInfo:
    def test_basic_attributes_set(self):
        cls = _make_provider_class(
            provider_name="Alpha", provider_key="ALPHA", is_cloud=True
        )
        info = ProviderInfo(cls)
        assert info.provider_name == "Alpha"
        assert info.provider_key == "ALPHA"
        assert info.is_cloud is True

    def test_display_name_cloud(self):
        cls = _make_provider_class(is_cloud=True)
        info = ProviderInfo(cls)
        assert "Cloud" in info.display_name or "☁️" in info.display_name

    def test_display_name_local(self):
        cls = _make_provider_class(is_cloud=False)
        info = ProviderInfo(cls)
        assert "Local" in info.display_name or "💻" in info.display_name

    def test_to_dict_shape(self):
        cls = _make_provider_class(
            provider_name="Beta", provider_key="BETA", is_cloud=True
        )
        info = ProviderInfo(cls)
        d = info.to_dict()
        assert "value" in d
        assert "label" in d
        assert "is_cloud" in d
        assert d["value"] == "BETA"

    def test_provider_key_falls_back_to_class_name(self):
        """When provider_key is not an attribute, falls back to class name."""
        cls = MagicMock(spec=[])
        cls.provider_name = "Gamma"
        cls.__name__ = "GammaProvider"
        cls.is_cloud = True
        cls.requires_auth_for_models = MagicMock(return_value=True)
        info = ProviderInfo(cls)
        assert info.provider_key  # must be some non-empty string


# ---------------------------------------------------------------------------
# ProviderDiscovery singleton
# ---------------------------------------------------------------------------


class TestProviderDiscoverySingleton:
    def test_same_instance_returned(self):
        d1 = ProviderDiscovery()
        d2 = ProviderDiscovery()
        assert d1 is d2


# ---------------------------------------------------------------------------
# get_provider_info
# ---------------------------------------------------------------------------


class TestGetProviderInfo:
    def test_unknown_key_returns_none(self):
        discovery = ProviderDiscovery()
        # Force providers to be discovered first
        discovery.discover_providers()
        result = discovery.get_provider_info("NONEXISTENT_PROVIDER_XYZ")
        assert result is None

    def test_triggers_discover_if_not_done(self):
        """get_provider_info() discovers providers if not already done."""
        discovery = ProviderDiscovery()
        discovery._discovered = False
        # Just verifying it runs without error
        result = discovery.get_provider_info("NONEXISTENT_XYZ")
        assert result is None  # not found, but no crash


# ---------------------------------------------------------------------------
# get_provider_class
# ---------------------------------------------------------------------------


class TestGetProviderClass:
    def test_unknown_key_returns_none(self):
        result = get_provider_class("TOTALLY_NONEXISTENT_KEY_ZZZ")
        assert result is None


# ---------------------------------------------------------------------------
# get_provider_options sorting
# ---------------------------------------------------------------------------


class TestGetProviderOptions:
    def test_options_sorted_by_label(self):
        discovery = ProviderDiscovery()
        # Inject two fake providers with known labels
        cls_z = _make_provider_class(provider_name="Zebra", provider_key="Z")
        cls_a = _make_provider_class(provider_name="Apple", provider_key="A")
        info_z = ProviderInfo(cls_z)
        info_a = ProviderInfo(cls_a)

        original = dict(discovery._providers)
        discovery._providers = {"Z": info_z, "A": info_a}
        discovery._discovered = True
        try:
            options = discovery.get_provider_options()
            labels = [o["label"] for o in options]
            assert labels == sorted(labels)
        finally:
            discovery._providers = original


# ---------------------------------------------------------------------------
# get_available_provider_options – filters unavailable
# ---------------------------------------------------------------------------


class TestGetAvailableProviderOptions:
    def test_unavailable_providers_filtered_out(self):
        discovery = ProviderDiscovery()

        cls_ok = _make_provider_class(provider_name="OK", provider_key="OK")
        cls_ok.is_available = MagicMock(return_value=True)

        cls_bad = _make_provider_class(provider_name="BAD", provider_key="BAD")
        cls_bad.is_available = MagicMock(return_value=False)

        info_ok = ProviderInfo(cls_ok)
        info_ok.provider_class = cls_ok
        info_bad = ProviderInfo(cls_bad)
        info_bad.provider_class = cls_bad

        original = dict(discovery._providers)
        original_disc = discovery._discovered
        discovery._providers = {"OK": info_ok, "BAD": info_bad}
        discovery._discovered = True
        try:
            options = discovery.get_available_provider_options()
            values = [o["value"] for o in options]
            assert "OK" in values
            assert "BAD" not in values
        finally:
            discovery._providers = original
            discovery._discovered = original_disc


# ---------------------------------------------------------------------------
# Module-level convenience functions
# ---------------------------------------------------------------------------


class TestModuleLevelFunctions:
    def test_discover_providers_returns_dict(self):
        result = discover_providers()
        assert isinstance(result, dict)

    def test_get_discovered_provider_options_returns_list(self):
        result = get_discovered_provider_options()
        assert isinstance(result, list)

    def test_get_available_discovered_provider_options_returns_list(self):
        result = get_available_discovered_provider_options()
        assert isinstance(result, list)
