"""
Classe abstraite BaseStore définissant l'interface des stores.

Tous les stores (Amazon, Cdiscount, etc.) doivent hériter de BaseStore
et implémenter ses méthodes abstraites.
"""

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional

import yaml

from pricewatch.app.core.logging import get_logger
from pricewatch.app.core.schema import ProductSnapshot

logger = get_logger("stores.base")


class BaseStore(ABC):
    """
    Classe abstraite définissant l'interface d'un store.

    Chaque store (Amazon, Cdiscount, etc.) doit implémenter:
    - match(): Détection si une URL correspond au store
    - canonicalize(): Normalisation de l'URL
    - extract_reference(): Extraction de la référence produit
    - parse(): Parsing HTML vers ProductSnapshot

    Les sélecteurs CSS/XPath sont stockés dans selectors.yml pour
    faciliter la maintenance sans toucher au code Python.
    """

    def __init__(self, store_id: str, selectors_path: Optional[Path] = None):
        """
        Initialise le store.

        Args:
            store_id: Identifiant unique du store (ex: 'amazon', 'cdiscount')
            selectors_path: Chemin vers le fichier selectors.yml
        """
        self.store_id = store_id
        self.selectors: dict = {}

        if selectors_path and selectors_path.exists():
            self._load_selectors(selectors_path)

    def _load_selectors(self, path: Path) -> None:
        """
        Charge les sélecteurs depuis le fichier YAML.

        Args:
            path: Chemin vers selectors.yml
        """
        try:
            with open(path, "r", encoding="utf-8") as f:
                self.selectors = yaml.safe_load(f) or {}
            logger.debug(f"[{self.store_id}] Sélecteurs chargés: {len(self.selectors)} entrées")
        except Exception as e:
            logger.warning(f"[{self.store_id}] Erreur chargement sélecteurs: {e}")
            self.selectors = {}

    @abstractmethod
    def match(self, url: str) -> float:
        """
        Retourne un score de correspondance entre l'URL et ce store.

        Args:
            url: URL à tester

        Returns:
            Score entre 0.0 (aucune correspondance) et 1.0 (correspondance parfaite)

        Exemple:
            - 'amazon.fr' dans l'URL → 0.9
            - 'amazon.com' dans l'URL → 0.8
            - Autres domaines → 0.0

        Justification technique:
        - Score plutôt que booléen pour gérer les ambiguïtés (ex: sous-domaines)
        - Le Registry choisira le store avec le meilleur score
        """
        pass

    @abstractmethod
    def canonicalize(self, url: str) -> str:
        """
        Normalise l'URL vers sa forme canonique.

        Args:
            url: URL brute (peut contenir des query params, ref, etc.)

        Returns:
            URL canonique (ex: https://www.amazon.fr/dp/B08N5WRWNW)

        Justification technique:
        - Évite les doublons dans la base de données
        - Facilite le suivi d'un même produit dans le temps
        - Supprime les paramètres de tracking
        """
        pass

    @abstractmethod
    def extract_reference(self, url: str) -> Optional[str]:
        """
        Extrait la référence produit depuis l'URL.

        Args:
            url: URL du produit

        Returns:
            Référence (ASIN pour Amazon, SKU pour autres) ou None

        Exemple:
            - Amazon: https://amazon.fr/dp/B08N5WRWNW → "B08N5WRWNW"
            - Cdiscount: https://cdiscount.com/.../f-123-sku.html → "123-sku"
        """
        pass

    @abstractmethod
    def parse(self, html: str, url: str) -> ProductSnapshot:
        """
        Parse le HTML et retourne un ProductSnapshot.

        Args:
            html: Contenu HTML de la page produit
            url: URL canonique du produit

        Returns:
            ProductSnapshot avec toutes les données extraites

        Raises:
            Exception: Si le parsing échoue complètement

        Justification technique:
        - Utilise self.selectors (chargés depuis YAML) pour extraire les données
        - En cas d'échec partiel, retourne un snapshot avec debug.status=partial
        - En cas d'échec total, raise une exception pour fallback Playwright
        """
        pass

    def get_selector(self, key: str, default: Optional[str] = None) -> Optional[str]:
        """
        Récupère un sélecteur depuis self.selectors.

        Args:
            key: Clé du sélecteur (ex: 'title', 'price')
            default: Valeur par défaut si non trouvé

        Returns:
            Sélecteur CSS ou XPath, ou default
        """
        return self.selectors.get(key, default)

    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} id={self.store_id}>"
