"""
Tests pour pricewatch.app.core.registry

Vérifie l'enregistrement des stores, la détection automatique,
et les fonctions helper du registry.
"""

import pytest

from pricewatch.app.core.registry import StoreRegistry
from pricewatch.app.stores.base import BaseStore
from pricewatch.app.core.schema import ProductSnapshot


class MockStore(BaseStore):
    """Mock store pour les tests."""

    def __init__(self, store_id: str, match_patterns: dict[str, float]):
        """
        Args:
            store_id: ID du store
            match_patterns: Dict {substring: score} pour simuler match()
        """
        super().__init__(store_id=store_id, selectors_path=None)
        self.match_patterns = match_patterns

    def match(self, url: str) -> float:
        """Retourne un score basé sur les patterns configurés."""
        if not url:
            return 0.0
        url_lower = url.lower()
        for pattern, score in self.match_patterns.items():
            if pattern in url_lower:
                return score
        return 0.0

    def canonicalize(self, url: str) -> str:
        """Mock canonicalize."""
        return url

    def extract_reference(self, url: str) -> str | None:
        """Mock extract_reference."""
        return "TEST_REF"

    def parse(self, html: str, url: str, **kwargs) -> ProductSnapshot:
        """Mock parse - pas utilisé dans les tests du registry."""
        raise NotImplementedError("Mock parse not implemented")


class TestStoreRegistry:
    """Tests du StoreRegistry."""

    @pytest.fixture
    def registry(self) -> StoreRegistry:
        """Fixture: Registry vide."""
        return StoreRegistry()

    @pytest.fixture
    def mock_amazon(self) -> MockStore:
        """Fixture: Mock Amazon store."""
        return MockStore(
            store_id="amazon",
            match_patterns={"amazon.fr": 0.9, "amazon.com": 0.8},
        )

    @pytest.fixture
    def mock_cdiscount(self) -> MockStore:
        """Fixture: Mock Cdiscount store."""
        return MockStore(
            store_id="cdiscount",
            match_patterns={"cdiscount.com": 0.9},
        )

    def test_registry_init_empty(self, registry):
        """Un registry vide ne contient aucun store."""
        assert len(registry) == 0
        assert registry.list_stores() == []

    def test_register_single_store(self, registry, mock_amazon):
        """Enregistre un seul store."""
        registry.register(mock_amazon)
        assert len(registry) == 1
        assert "amazon" in registry.list_stores()

    def test_register_multiple_stores(self, registry, mock_amazon, mock_cdiscount):
        """Enregistre plusieurs stores."""
        registry.register(mock_amazon)
        registry.register(mock_cdiscount)
        assert len(registry) == 2
        assert set(registry.list_stores()) == {"amazon", "cdiscount"}

    def test_register_invalid_type(self, registry):
        """Enregistrer un objet non-BaseStore doit échouer."""
        with pytest.raises(TypeError) as exc_info:
            registry.register("not a store")
        assert "Expected BaseStore" in str(exc_info.value)

    def test_register_duplicate_replaces(self, registry, mock_amazon):
        """Enregistrer deux fois le même store_id remplace le premier."""
        registry.register(mock_amazon)
        assert len(registry) == 1

        # Créer un autre mock avec le même ID
        duplicate = MockStore(store_id="amazon", match_patterns={"amazon.es": 0.7})
        registry.register(duplicate)

        # Doit toujours avoir un seul store
        assert len(registry) == 1
        assert "amazon" in registry.list_stores()

        # Doit avoir le nouveau store
        store = registry.get_store("amazon")
        assert store is duplicate

    def test_get_store_existing(self, registry, mock_amazon):
        """Récupère un store existant."""
        registry.register(mock_amazon)
        store = registry.get_store("amazon")
        assert store is mock_amazon

    def test_get_store_non_existing(self, registry):
        """Récupère un store inexistant retourne None."""
        store = registry.get_store("nonexistent")
        assert store is None

    def test_unregister_existing(self, registry, mock_amazon):
        """Désenregistre un store existant."""
        registry.register(mock_amazon)
        assert len(registry) == 1

        removed = registry.unregister("amazon")
        assert removed is True
        assert len(registry) == 0
        assert "amazon" not in registry.list_stores()

    def test_unregister_non_existing(self, registry):
        """Désenregistre un store inexistant retourne False."""
        removed = registry.unregister("nonexistent")
        assert removed is False

    def test_detect_store_empty_url(self, registry, mock_amazon):
        """URL vide retourne None."""
        registry.register(mock_amazon)
        store = registry.detect_store("")
        assert store is None

    def test_detect_store_whitespace_url(self, registry, mock_amazon):
        """URL avec espaces retourne None."""
        registry.register(mock_amazon)
        store = registry.detect_store("   ")
        assert store is None

    def test_detect_store_empty_registry(self, registry):
        """Registry vide retourne None."""
        store = registry.detect_store("https://example.com")
        assert store is None

    def test_detect_store_single_match(self, registry, mock_amazon):
        """Détecte un store avec un seul match."""
        registry.register(mock_amazon)
        store = registry.detect_store("https://www.amazon.fr/dp/B08N5WRWNW")
        assert store is mock_amazon

    def test_detect_store_no_match(self, registry, mock_amazon):
        """Aucun store ne match retourne None."""
        registry.register(mock_amazon)
        store = registry.detect_store("https://www.ebay.com/item/123")
        assert store is None

    def test_detect_store_multiple_matches_best_score(
        self, registry, mock_amazon, mock_cdiscount
    ):
        """Avec plusieurs matches, retourne le meilleur score."""
        registry.register(mock_amazon)
        registry.register(mock_cdiscount)

        # Test Amazon
        store = registry.detect_store("https://www.amazon.fr/dp/B08N5WRWNW")
        assert store is mock_amazon

        # Test Cdiscount
        store = registry.detect_store("https://www.cdiscount.com/product/123")
        assert store is mock_cdiscount

    def test_detect_store_ambiguous_url_best_score(self, registry):
        """URL ambiguë: retourne le store avec le meilleur score."""
        # Créer deux stores avec des scores différents pour la même URL
        store_a = MockStore(store_id="store_a", match_patterns={"example.com": 0.7})
        store_b = MockStore(store_id="store_b", match_patterns={"example.com": 0.9})

        registry.register(store_a)
        registry.register(store_b)

        store = registry.detect_store("https://www.example.com")
        assert store is store_b  # Meilleur score (0.9 vs 0.7)

    def test_detect_store_exception_in_match(self, registry, mock_amazon):
        """Si un store.match() lève une exception, continue avec les autres."""
        # Créer un store qui crash
        class BrokenStore(MockStore):
            def match(self, url: str) -> float:
                raise RuntimeError("Simulated crash")

        broken = BrokenStore(store_id="broken", match_patterns={})

        registry.register(broken)
        registry.register(mock_amazon)

        # Doit quand même détecter Amazon malgré le crash du broken store
        store = registry.detect_store("https://www.amazon.fr/dp/B08N5WRWNW")
        assert store is mock_amazon

    def test_list_stores_empty(self, registry):
        """Liste des stores vide."""
        assert registry.list_stores() == []

    def test_list_stores_multiple(self, registry, mock_amazon, mock_cdiscount):
        """Liste des stores avec plusieurs enregistrés."""
        registry.register(mock_amazon)
        registry.register(mock_cdiscount)
        stores = registry.list_stores()
        assert len(stores) == 2
        assert "amazon" in stores
        assert "cdiscount" in stores

    def test_len_operator(self, registry, mock_amazon, mock_cdiscount):
        """Opérateur len() retourne le nombre de stores."""
        assert len(registry) == 0

        registry.register(mock_amazon)
        assert len(registry) == 1

        registry.register(mock_cdiscount)
        assert len(registry) == 2

        registry.unregister("amazon")
        assert len(registry) == 1

    def test_repr(self, registry, mock_amazon, mock_cdiscount):
        """Représentation string du registry."""
        registry.register(mock_amazon)
        registry.register(mock_cdiscount)
        repr_str = repr(registry)
        assert "StoreRegistry" in repr_str
        assert "amazon" in repr_str
        assert "cdiscount" in repr_str


class TestRegistryGlobalFunctions:
    """Tests des fonctions globales du module registry."""

    def test_get_registry_singleton(self):
        """get_registry() retourne toujours la même instance."""
        from pricewatch.app.core.registry import get_registry

        registry1 = get_registry()
        registry2 = get_registry()
        assert registry1 is registry2

    def test_register_store_global(self):
        """register_store() enregistre dans le registry global."""
        from pricewatch.app.core.registry import get_registry, register_store

        # Nettoyer le registry global pour le test
        registry = get_registry()
        initial_count = len(registry)

        mock = MockStore(store_id="test_global", match_patterns={})
        register_store(mock)

        assert len(registry) == initial_count + 1
        assert "test_global" in registry.list_stores()

        # Cleanup
        registry.unregister("test_global")

    def test_detect_store_global(self):
        """detect_store() utilise le registry global."""
        from pricewatch.app.core.registry import detect_store, get_registry, register_store

        # Nettoyer le registry global pour le test
        registry = get_registry()

        mock = MockStore(store_id="test_detect", match_patterns={"testsite.com": 0.9})
        register_store(mock)

        store = detect_store("https://www.testsite.com/product")
        assert store is not None
        assert store.store_id == "test_detect"

        # Cleanup
        registry.unregister("test_detect")
