"""
Modèles de données Pydantic pour PriceWatch.

Ce module définit ProductSnapshot, le modèle canonique représentant
toutes les informations récupérées lors du scraping d'un produit.
"""

from datetime import datetime
from enum import Enum
from typing import Optional

from pydantic import BaseModel, ConfigDict, Field, HttpUrl, field_validator


class StockStatus(str, Enum):
    """Statut de disponibilité du produit."""

    IN_STOCK = "in_stock"
    OUT_OF_STOCK = "out_of_stock"
    UNKNOWN = "unknown"


class FetchMethod(str, Enum):
    """Méthode utilisée pour récupérer la page."""

    HTTP = "http"
    PLAYWRIGHT = "playwright"


class DebugStatus(str, Enum):
    """Statut technique de la récupération."""

    SUCCESS = "success"  # Récupération complète et parsing réussi
    PARTIAL = "partial"  # Récupération OK mais parsing incomplet
    FAILED = "failed"  # Échec complet (403, timeout, captcha, etc.)


class DebugInfo(BaseModel):
    """Informations de debug pour tracer les problèmes de scraping."""

    model_config = ConfigDict(use_enum_values=True)

    method: FetchMethod = Field(
        description="Méthode utilisée pour la récupération (http ou playwright)"
    )
    status: DebugStatus = Field(description="Statut de la récupération")
    errors: list[str] = Field(
        default_factory=list, description="Liste des erreurs rencontrées"
    )
    notes: list[str] = Field(
        default_factory=list, description="Notes techniques sur la récupération"
    )
    duration_ms: Optional[int] = Field(
        default=None, description="Durée de la récupération en millisecondes"
    )
    html_size_bytes: Optional[int] = Field(
        default=None, description="Taille du HTML récupéré en octets"
    )


class ProductSnapshot(BaseModel):
    """
    Modèle canonique représentant un produit scraped à un instant T.

    Ce modèle unifie les données de tous les stores (Amazon, Cdiscount, etc.)
    dans une structure commune. Les champs peuvent être null si l'information
    n'est pas disponible sur le site source.
    """

    # Métadonnées
    source: str = Field(
        description="Identifiant du store source (amazon, cdiscount, unknown)"
    )
    url: str = Field(description="URL canonique du produit")
    fetched_at: datetime = Field(
        default_factory=datetime.now,
        description="Date et heure de récupération (ISO 8601)",
    )

    # Données produit principales
    title: Optional[str] = Field(default=None, description="Nom du produit")
    price: Optional[float] = Field(default=None, description="Prix du produit", ge=0)
    msrp: Optional[float] = Field(default=None, description="Prix conseille", ge=0)
    currency: str = Field(default="EUR", description="Devise (EUR, USD, etc.)")
    shipping_cost: Optional[float] = Field(
        default=None, description="Frais de port", ge=0
    )
    stock_status: StockStatus = Field(
        default=StockStatus.UNKNOWN, description="Statut de disponibilité"
    )

    # Identifiants et catégorisation
    reference: Optional[str] = Field(
        default=None, description="Référence produit (ASIN, SKU, etc.)"
    )
    category: Optional[str] = Field(default=None, description="Catégorie du produit")
    description: Optional[str] = Field(default=None, description="Description produit")

    # Médias
    images: list[str] = Field(
        default_factory=list, description="Liste des URLs d'images du produit"
    )

    # Caractéristiques techniques
    specs: dict[str, str] = Field(
        default_factory=dict,
        description="Caractéristiques techniques (clé/valeur)",
    )

    # Debug et traçabilité
    debug: DebugInfo = Field(
        description="Informations de debug pour traçabilité"
    )

    @field_validator("url")
    @classmethod
    def validate_url(cls, v: str) -> str:
        """Valide que l'URL n'est pas vide."""
        if not v or not v.strip():
            raise ValueError("URL cannot be empty")
        return v.strip()

    @field_validator("source")
    @classmethod
    def validate_source(cls, v: str) -> str:
        """Valide et normalise le nom du store."""
        if not v or not v.strip():
            raise ValueError("Source cannot be empty")
        return v.strip().lower()

    @field_validator("images")
    @classmethod
    def validate_images(cls, v: list[str]) -> list[str]:
        """Filtre les URLs d'images vides."""
        return [url.strip() for url in v if url and url.strip()]

    model_config = ConfigDict(
        use_enum_values=True,
        json_schema_extra={
            "example": {
                "source": "amazon",
                "url": "https://www.amazon.fr/dp/B08N5WRWNW",
                "fetched_at": "2026-01-13T10:30:00Z",
                "title": "Exemple de produit",
                "price": 299.99,
                "msrp": 349.99,
                "currency": "EUR",
                "shipping_cost": 0.0,
                "stock_status": "in_stock",
                "reference": "B08N5WRWNW",
                "category": "Electronics",
                "description": "Chargeur USB-C multi-ports.",
                "images": [
                    "https://example.com/image1.jpg",
                    "https://example.com/image2.jpg",
                ],
                "specs": {
                    "Marque": "ExampleBrand",
                    "Couleur": "Noir",
                    "Poids": "2.5 kg",
                },
                "debug": {
                    "method": "http",
                    "status": "success",
                    "errors": [],
                    "notes": ["Récupération réussie du premier coup"],
                    "duration_ms": 1250,
                    "html_size_bytes": 145000,
                },
            }
        },
    )

    def to_dict(self) -> dict:
        """Serialize vers un dictionnaire Python natif."""
        return self.model_dump(mode="json")

    def to_json(self, **kwargs) -> str:
        """Serialize vers JSON."""
        return self.model_dump_json(**kwargs)

    @classmethod
    def from_json(cls, json_str: str) -> "ProductSnapshot":
        """Désérialise depuis JSON."""
        return cls.model_validate_json(json_str)

    def is_complete(self) -> bool:
        """
        Vérifie si le snapshot contient au minimum les données essentielles.

        Retourne True si title ET price sont présents.
        """
        return self.title is not None and self.price is not None

    def add_error(self, error: str) -> None:
        """Ajoute une erreur au debug."""
        self.debug.errors.append(error)

    def add_note(self, note: str) -> None:
        """Ajoute une note au debug."""
        self.debug.notes.append(note)
