"""
Repository pattern pour la persistence SQLAlchemy.

Centralise les operations CRUD sur les modeles DB a partir d'un ProductSnapshot.
"""

from __future__ import annotations

from typing import Optional

from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session

from pricewatch.app.core.logging import get_logger
from pricewatch.app.core.schema import ProductSnapshot
from pricewatch.app.db.models import PriceHistory, Product, ProductImage, ProductSpec, ScrapingLog

logger = get_logger("db.repository")


class ProductRepository:
    """Repository de persistence pour ProductSnapshot."""

    def __init__(self, session: Session) -> None:
        self.session = session

    def get_or_create(self, source: str, reference: str, url: str) -> Product:
        """
        Recuperer ou creer un produit par cle naturelle (source, reference).
        """
        product = (
            self.session.query(Product)
            .filter(Product.source == source, Product.reference == reference)
            .one_or_none()
        )
        if product:
            return product

        product = Product(source=source, reference=reference, url=url)
        self.session.add(product)
        self.session.flush()
        return product

    def update_product_metadata(self, product: Product, snapshot: ProductSnapshot) -> None:
        """Met a jour les metadonnees produit si disponibles."""
        if snapshot.url:
            product.url = snapshot.url
        if snapshot.title:
            product.title = snapshot.title
        if snapshot.category:
            product.category = snapshot.category
        if snapshot.description:
            product.description = snapshot.description
        if snapshot.currency:
            product.currency = snapshot.currency
        if snapshot.msrp is not None:
            product.msrp = snapshot.msrp

    def add_price_history(self, product: Product, snapshot: ProductSnapshot) -> Optional[PriceHistory]:
        """Ajoute une entree d'historique de prix si inexistante."""
        existing = (
            self.session.query(PriceHistory)
            .filter(
                PriceHistory.product_id == product.id,
                PriceHistory.fetched_at == snapshot.fetched_at,
            )
            .one_or_none()
        )
        if existing:
            return existing

        price_entry = PriceHistory(
            product_id=product.id,
            price=snapshot.price,
            shipping_cost=snapshot.shipping_cost,
            stock_status=snapshot.stock_status,
            fetch_method=snapshot.debug.method,
            fetch_status=snapshot.debug.status,
            fetched_at=snapshot.fetched_at,
        )
        self.session.add(price_entry)
        return price_entry

    def sync_images(self, product: Product, images: list[str]) -> None:
        """Synchronise les images (ajout des nouvelles)."""
        existing_urls = {image.image_url for image in product.images}
        for position, url in enumerate(images):
            if url in existing_urls:
                continue
            self.session.add(ProductImage(product_id=product.id, image_url=url, position=position))

    def sync_specs(self, product: Product, specs: dict[str, str]) -> None:
        """Synchronise les specs (upsert par cle)."""
        existing_specs = {spec.spec_key: spec for spec in product.specs}
        for key, value in specs.items():
            if key in existing_specs:
                existing_specs[key].spec_value = value
            else:
                self.session.add(ProductSpec(product_id=product.id, spec_key=key, spec_value=value))

    def add_scraping_log(self, snapshot: ProductSnapshot, product_id: Optional[int]) -> ScrapingLog:
        """Ajoute un log de scraping pour observabilite."""
        log_entry = ScrapingLog(
            product_id=product_id,
            url=snapshot.url,
            source=snapshot.source,
            reference=snapshot.reference,
            fetch_method=snapshot.debug.method,
            fetch_status=snapshot.debug.status,
            fetched_at=snapshot.fetched_at,
            duration_ms=snapshot.debug.duration_ms,
            html_size_bytes=snapshot.debug.html_size_bytes,
            errors=snapshot.debug.errors or None,
            notes=snapshot.debug.notes or None,
        )
        self.session.add(log_entry)
        return log_entry

    def save_snapshot(self, snapshot: ProductSnapshot) -> Optional[int]:
        """
        Persiste un ProductSnapshot complet dans la base.

        Retourne l'id produit ou None si reference absente.
        """
        if not snapshot.reference:
            logger.warning("Reference absente: persistence ignoree")
            self.add_scraping_log(snapshot, product_id=None)
            return None

        product = self.get_or_create(snapshot.source, snapshot.reference, snapshot.url)
        self.update_product_metadata(product, snapshot)
        self.add_price_history(product, snapshot)
        self.sync_images(product, snapshot.images)
        self.sync_specs(product, snapshot.specs)
        self.add_scraping_log(snapshot, product_id=product.id)
        return product.id

    def safe_save_snapshot(self, snapshot: ProductSnapshot) -> Optional[int]:
        """Sauvegarde avec gestion d'erreur SQLAlchemy."""
        try:
            return self.save_snapshot(snapshot)
        except SQLAlchemyError as exc:
            logger.error(f"Erreur SQLAlchemy: {exc}")
            raise
