"""
Tests pour pricewatch.app.core.schema

Vérifie la validation Pydantic, la serialization JSON,
et les méthodes helper de ProductSnapshot.
"""

import json
from datetime import datetime

import pytest
from pydantic import ValidationError

from pricewatch.app.core.schema import (
    DebugInfo,
    DebugStatus,
    FetchMethod,
    ProductSnapshot,
    StockStatus,
)


class TestEnums:
    """Tests des enums."""

    def test_stock_status_values(self):
        """Vérifie les valeurs de StockStatus."""
        assert StockStatus.IN_STOCK.value == "in_stock"
        assert StockStatus.OUT_OF_STOCK.value == "out_of_stock"
        assert StockStatus.UNKNOWN.value == "unknown"

    def test_fetch_method_values(self):
        """Vérifie les valeurs de FetchMethod."""
        assert FetchMethod.HTTP.value == "http"
        assert FetchMethod.PLAYWRIGHT.value == "playwright"

    def test_debug_status_values(self):
        """Vérifie les valeurs de DebugStatus."""
        assert DebugStatus.SUCCESS.value == "success"
        assert DebugStatus.PARTIAL.value == "partial"
        assert DebugStatus.FAILED.value == "failed"


class TestDebugInfo:
    """Tests du modèle DebugInfo."""

    def test_debug_info_creation(self):
        """Crée un DebugInfo valide."""
        debug = DebugInfo(
            method=FetchMethod.HTTP,
            status=DebugStatus.SUCCESS,
            duration_ms=1500,
            html_size_bytes=120000,
        )
        assert debug.method == FetchMethod.HTTP
        assert debug.status == DebugStatus.SUCCESS
        assert debug.duration_ms == 1500
        assert debug.html_size_bytes == 120000
        assert debug.errors == []
        assert debug.notes == []

    def test_debug_info_with_errors(self):
        """Crée un DebugInfo avec des erreurs."""
        debug = DebugInfo(
            method=FetchMethod.PLAYWRIGHT,
            status=DebugStatus.FAILED,
            errors=["403 Forbidden", "Captcha detected"],
            notes=["Fallback to Playwright triggered"],
        )
        assert len(debug.errors) == 2
        assert "403 Forbidden" in debug.errors
        assert len(debug.notes) == 1

    def test_debug_info_defaults(self):
        """Vérifie les valeurs par défaut."""
        debug = DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS)
        assert debug.errors == []
        assert debug.notes == []
        assert debug.duration_ms is None
        assert debug.html_size_bytes is None


class TestProductSnapshot:
    """Tests du modèle ProductSnapshot."""

    @pytest.fixture
    def minimal_snapshot(self) -> ProductSnapshot:
        """Fixture: ProductSnapshot minimal valide."""
        return ProductSnapshot(
            source="amazon",
            url="https://www.amazon.fr/dp/B08N5WRWNW",
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )

    @pytest.fixture
    def complete_snapshot(self) -> ProductSnapshot:
        """Fixture: ProductSnapshot complet."""
        return ProductSnapshot(
            source="amazon",
            url="https://www.amazon.fr/dp/B08N5WRWNW",
            fetched_at=datetime(2026, 1, 13, 10, 30, 0),
            title="PlayStation 5",
            price=499.99,
            currency="EUR",
            shipping_cost=0.0,
            stock_status=StockStatus.IN_STOCK,
            reference="B08N5WRWNW",
            category="Jeux vidéo",
            images=[
                "https://example.com/image1.jpg",
                "https://example.com/image2.jpg",
            ],
            specs={
                "Marque": "Sony",
                "Couleur": "Blanc",
                "Poids": "4.5 kg",
            },
            debug=DebugInfo(
                method=FetchMethod.HTTP,
                status=DebugStatus.SUCCESS,
                duration_ms=1200,
                html_size_bytes=145000,
            ),
        )

    def test_create_minimal_snapshot(self, minimal_snapshot):
        """Crée un ProductSnapshot minimal."""
        assert minimal_snapshot.source == "amazon"
        assert minimal_snapshot.url == "https://www.amazon.fr/dp/B08N5WRWNW"
        assert minimal_snapshot.title is None
        assert minimal_snapshot.price is None
        assert minimal_snapshot.currency == "EUR"  # Default
        assert minimal_snapshot.stock_status == StockStatus.UNKNOWN  # Default

    def test_create_complete_snapshot(self, complete_snapshot):
        """Crée un ProductSnapshot complet."""
        assert complete_snapshot.source == "amazon"
        assert complete_snapshot.title == "PlayStation 5"
        assert complete_snapshot.price == 499.99
        assert complete_snapshot.reference == "B08N5WRWNW"
        assert len(complete_snapshot.images) == 2
        assert len(complete_snapshot.specs) == 3

    def test_url_validation_empty(self):
        """URL vide doit échouer."""
        with pytest.raises(ValidationError) as exc_info:
            ProductSnapshot(
                source="amazon",
                url="",
                debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
            )
        assert "URL cannot be empty" in str(exc_info.value)

    def test_url_validation_whitespace(self):
        """URL avec seulement des espaces doit échouer."""
        with pytest.raises(ValidationError) as exc_info:
            ProductSnapshot(
                source="amazon",
                url="   ",
                debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
            )
        assert "URL cannot be empty" in str(exc_info.value)

    def test_source_validation_empty(self):
        """Source vide doit échouer."""
        with pytest.raises(ValidationError) as exc_info:
            ProductSnapshot(
                source="",
                url="https://example.com",
                debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
            )
        assert "Source cannot be empty" in str(exc_info.value)

    def test_source_normalization(self):
        """Source doit être normalisée en lowercase."""
        snapshot = ProductSnapshot(
            source="AMAZON",
            url="https://example.com",
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )
        assert snapshot.source == "amazon"

    def test_price_negative(self):
        """Prix négatif doit échouer."""
        with pytest.raises(ValidationError):
            ProductSnapshot(
                source="amazon",
                url="https://example.com",
                price=-10.0,
                debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
            )

    def test_shipping_cost_negative(self):
        """Frais de port négatifs doivent échouer."""
        with pytest.raises(ValidationError):
            ProductSnapshot(
                source="amazon",
                url="https://example.com",
                shipping_cost=-5.0,
                debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
            )

    def test_images_validation(self):
        """Les URLs d'images vides doivent être filtrées."""
        snapshot = ProductSnapshot(
            source="amazon",
            url="https://example.com",
            images=["https://img1.jpg", "", "  ", "https://img2.jpg"],
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )
        assert len(snapshot.images) == 2
        assert "https://img1.jpg" in snapshot.images
        assert "https://img2.jpg" in snapshot.images

    def test_is_complete_with_title_and_price(self, complete_snapshot):
        """Un snapshot avec titre et prix est complet."""
        assert complete_snapshot.is_complete() is True

    def test_is_complete_without_price(self, minimal_snapshot):
        """Un snapshot sans prix n'est pas complet."""
        minimal_snapshot.title = "Test Product"
        assert minimal_snapshot.is_complete() is False

    def test_is_complete_without_title(self, minimal_snapshot):
        """Un snapshot sans titre n'est pas complet."""
        minimal_snapshot.price = 99.99
        assert minimal_snapshot.is_complete() is False

    def test_is_complete_minimal(self, minimal_snapshot):
        """Un snapshot minimal n'est pas complet."""
        assert minimal_snapshot.is_complete() is False

    def test_add_error(self, minimal_snapshot):
        """Ajoute une erreur au debug."""
        minimal_snapshot.add_error("Test error 1")
        minimal_snapshot.add_error("Test error 2")
        assert len(minimal_snapshot.debug.errors) == 2
        assert "Test error 1" in minimal_snapshot.debug.errors

    def test_add_note(self, minimal_snapshot):
        """Ajoute une note au debug."""
        minimal_snapshot.add_note("Test note 1")
        minimal_snapshot.add_note("Test note 2")
        assert len(minimal_snapshot.debug.notes) == 2
        assert "Test note 1" in minimal_snapshot.debug.notes

    def test_to_dict(self, complete_snapshot):
        """Serialization vers dict."""
        data = complete_snapshot.to_dict()
        assert isinstance(data, dict)
        assert data["source"] == "amazon"
        assert data["title"] == "PlayStation 5"
        assert data["price"] == 499.99
        assert isinstance(data["fetched_at"], str)  # ISO format
        assert data["debug"]["method"] == "http"

    def test_to_json(self, complete_snapshot):
        """Serialization vers JSON."""
        json_str = complete_snapshot.to_json()
        assert isinstance(json_str, str)

        # Vérifie que c'est du JSON valide
        data = json.loads(json_str)
        assert data["source"] == "amazon"
        assert data["title"] == "PlayStation 5"
        assert data["price"] == 499.99

    def test_from_json(self, complete_snapshot):
        """Désérialisation depuis JSON."""
        # Serialize puis deserialize
        json_str = complete_snapshot.to_json()
        restored = ProductSnapshot.from_json(json_str)

        assert restored.source == complete_snapshot.source
        assert restored.title == complete_snapshot.title
        assert restored.price == complete_snapshot.price
        assert restored.reference == complete_snapshot.reference

    def test_to_dict_and_from_json_roundtrip(self, complete_snapshot):
        """Roundtrip complet dict → JSON → ProductSnapshot."""
        # to_dict puis JSON puis from_json
        json_str = json.dumps(complete_snapshot.to_dict())
        restored = ProductSnapshot.from_json(json_str)

        assert restored.source == complete_snapshot.source
        assert restored.title == complete_snapshot.title
        assert restored.price == complete_snapshot.price

    def test_enum_serialization(self):
        """Les enums doivent être sérialisés en string."""
        snapshot = ProductSnapshot(
            source="amazon",
            url="https://example.com",
            stock_status=StockStatus.IN_STOCK,
            debug=DebugInfo(method=FetchMethod.PLAYWRIGHT, status=DebugStatus.PARTIAL),
        )

        data = snapshot.to_dict()
        assert data["stock_status"] == "in_stock"
        assert data["debug"]["method"] == "playwright"
        assert data["debug"]["status"] == "partial"

    def test_fetched_at_default(self):
        """fetched_at doit avoir une valeur par défaut."""
        snapshot = ProductSnapshot(
            source="amazon",
            url="https://example.com",
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )
        assert snapshot.fetched_at is not None
        assert isinstance(snapshot.fetched_at, datetime)

    def test_specs_default(self):
        """specs doit être un dict vide par défaut."""
        snapshot = ProductSnapshot(
            source="amazon",
            url="https://example.com",
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )
        assert snapshot.specs == {}
        assert isinstance(snapshot.specs, dict)

    def test_images_default(self):
        """images doit être une liste vide par défaut."""
        snapshot = ProductSnapshot(
            source="amazon",
            url="https://example.com",
            debug=DebugInfo(method=FetchMethod.HTTP, status=DebugStatus.SUCCESS),
        )
        assert snapshot.images == []
        assert isinstance(snapshot.images, list)
