"""
Tests for the ProviderRegistry singleton.
"""

import pytest
import threading

from services.providers.registry import ProviderRegistry, get_registry
from services.providers.base import TranslationProvider
from services.providers.schemas import TranslationRequest, TranslationResponse


class MockProvider(TranslationProvider):
    """Mock provider for testing."""

    def __init__(self, name: str, available: bool = True):
        self._name = name
        self._available = available

    def translate_text(self, request: TranslationRequest) -> TranslationResponse:
        return TranslationResponse(
            translated_text=f"[{self._name}] {request.text}",
            provider_name=self._name,
            from_cache=False,
        )

    def get_name(self) -> str:
        return self._name

    def is_available(self) -> bool:
        return self._available


class TestProviderRegistry:
    """Tests for the ProviderRegistry class."""

    @pytest.fixture(autouse=True)
    def setup_registry(self):
        """Clear registry before each test."""
        registry = get_registry()
        registry.clear()
        yield
        registry.clear()

    def test_singleton_pattern(self):
        """Test that ProviderRegistry is a singleton."""
        registry1 = ProviderRegistry()
        registry2 = ProviderRegistry()

        assert registry1 is registry2

    def test_get_registry_function(self):
        """Test get_registry returns the singleton."""
        registry1 = get_registry()
        registry2 = get_registry()

        assert registry1 is registry2

    def test_register_and_get(self):
        """Test registering and retrieving a provider."""
        registry = get_registry()
        provider = MockProvider("test")

        registry.register("test", provider)
        retrieved = registry.get("test")

        assert retrieved is provider
        assert retrieved.get_name() == "test"

    def test_get_nonexistent_provider(self):
        """Test getting a provider that doesn't exist."""
        registry = get_registry()

        result = registry.get("nonexistent")

        assert result is None

    def test_unregister(self):
        """Test unregistering a provider."""
        registry = get_registry()
        provider = MockProvider("test")

        registry.register("test", provider)
        assert registry.get("test") is not None

        result = registry.unregister("test")

        assert result is True
        assert registry.get("test") is None

    def test_unregister_nonexistent(self):
        """Test unregistering a provider that doesn't exist."""
        registry = get_registry()

        result = registry.unregister("nonexistent")

        assert result is False

    def test_list_all(self):
        """Test listing all registered providers."""
        registry = get_registry()
        registry.register("google", MockProvider("google"))
        registry.register("deepl", MockProvider("deepl"))
        registry.register("openai", MockProvider("openai"))

        names = registry.list_all()

        assert len(names) == 3
        assert "google" in names
        assert "deepl" in names
        assert "openai" in names

    def test_list_available(self):
        """Test listing only available providers."""
        registry = get_registry()
        registry.register("available1", MockProvider("available1", available=True))
        registry.register("available2", MockProvider("available2", available=True))
        registry.register("unavailable", MockProvider("unavailable", available=False))

        names = registry.list_available()

        assert len(names) == 2
        assert "available1" in names
        assert "available2" in names
        assert "unavailable" not in names

    def test_get_first_available(self):
        """Test getting first available provider from a list."""
        registry = get_registry()
        registry.register("google", MockProvider("google", available=True))
        registry.register("deepl", MockProvider("deepl", available=True))
        registry.register("openai", MockProvider("openai", available=False))

        provider = registry.get_first_available(["openai", "deepl", "google"])

        assert provider is not None
        assert provider.get_name() == "deepl"

    def test_get_first_available_all_unavailable(self):
        """Test getting first available when all are unavailable."""
        registry = get_registry()
        registry.register("google", MockProvider("google", available=False))
        registry.register("deepl", MockProvider("deepl", available=False))

        provider = registry.get_first_available(["google", "deepl"])

        assert provider is None

    def test_get_first_available_not_registered(self):
        """Test getting first available when provider not registered."""
        registry = get_registry()

        provider = registry.get_first_available(["google", "deepl"])

        assert provider is None

    def test_clear(self):
        """Test clearing all providers."""
        registry = get_registry()
        registry.register("google", MockProvider("google"))
        registry.register("deepl", MockProvider("deepl"))

        registry.clear()

        assert len(registry) == 0
        assert registry.list_all() == []

    def test_len(self):
        """Test __len__ method."""
        registry = get_registry()

        assert len(registry) == 0

        registry.register("google", MockProvider("google"))
        assert len(registry) == 1

        registry.register("deepl", MockProvider("deepl"))
        assert len(registry) == 2

    def test_contains(self):
        """Test __contains__ method."""
        registry = get_registry()
        registry.register("google", MockProvider("google"))

        assert "google" in registry
        assert "deepl" not in registry

    def test_thread_safety(self):
        """Test that registry operations are thread-safe."""
        registry = get_registry()
        registry.clear()
        errors = []

        def register_providers(prefix: str, count: int):
            try:
                for i in range(count):
                    registry.register(f"{prefix}_{i}", MockProvider(f"{prefix}_{i}"))
            except Exception as e:
                errors.append(e)

        threads = [
            threading.Thread(target=register_providers, args=(f"thread_{t}", 10))
            for t in range(5)
        ]

        for t in threads:
            t.start()
        for t in threads:
            t.join()

        assert len(errors) == 0
        assert len(registry) == 50

    def test_overwrite_registration(self):
        """Test that registering with same name overwrites."""
        registry = get_registry()
        provider1 = MockProvider("test1")
        provider2 = MockProvider("test2")

        registry.register("test", provider1)
        assert registry.get("test").get_name() == "test1"

        registry.register("test", provider2)
        assert registry.get("test").get_name() == "test2"
