"""Tests fonctions API produits avec mocks."""

from datetime import datetime
from unittest.mock import MagicMock, patch

import pytest
from fastapi import HTTPException
from sqlalchemy.exc import IntegrityError, SQLAlchemyError

from pricewatch.app.api.main import (
    create_product,
    get_product,
    update_product,
    delete_product,
    list_prices,
    create_price,
    update_price,
    delete_price,
)
from pricewatch.app.api.schemas import ProductCreate, ProductUpdate, PriceHistoryCreate, PriceHistoryUpdate


class MockProduct:
    """Mock Product model."""
    def __init__(self, **kwargs):
        self.id = kwargs.get("id", 1)
        self.source = kwargs.get("source", "amazon")
        self.reference = kwargs.get("reference", "REF123")
        self.url = kwargs.get("url", "https://example.com")
        self.title = kwargs.get("title", "Test Product")
        self.category = kwargs.get("category")
        self.description = kwargs.get("description")
        self.currency = kwargs.get("currency", "EUR")
        self.msrp = kwargs.get("msrp")
        self.first_seen_at = kwargs.get("first_seen_at", datetime.now())
        self.last_updated_at = kwargs.get("last_updated_at", datetime.now())


class MockPrice:
    """Mock PriceHistory model."""
    def __init__(self, **kwargs):
        self.id = kwargs.get("id", 1)
        self.product_id = kwargs.get("product_id", 1)
        self.price = kwargs.get("price", 99.99)
        self.shipping_cost = kwargs.get("shipping_cost")
        self.stock_status = kwargs.get("stock_status", "in_stock")
        self.fetch_method = kwargs.get("fetch_method", "http")
        self.fetch_status = kwargs.get("fetch_status", "success")
        self.fetched_at = kwargs.get("fetched_at", datetime.now())


class TestCreateProduct:
    """Tests create_product."""

    def test_create_success(self):
        """Cree un produit avec succes."""
        session = MagicMock()
        session.add = MagicMock()
        session.commit = MagicMock()
        session.refresh = MagicMock()

        payload = ProductCreate(
            source="amazon",
            reference="NEW123",
            url="https://amazon.fr/dp/NEW123",
            title="New Product",
            currency="EUR",
        )

        with patch("pricewatch.app.api.main.Product") as MockProductClass:
            mock_product = MockProduct(reference="NEW123")
            MockProductClass.return_value = mock_product

            with patch("pricewatch.app.api.main._product_to_out") as mock_to_out:
                mock_to_out.return_value = MagicMock()
                result = create_product(payload, session)

                session.add.assert_called_once()
                session.commit.assert_called_once()

    def test_create_duplicate(self):
        """Cree un produit duplique leve 409."""
        session = MagicMock()
        session.add = MagicMock()
        session.commit = MagicMock(side_effect=IntegrityError("duplicate", {}, None))
        session.rollback = MagicMock()

        payload = ProductCreate(
            source="amazon",
            reference="DUPE",
            url="https://amazon.fr/dp/DUPE",
            title="Duplicate",
            currency="EUR",
        )

        with patch("pricewatch.app.api.main.Product"):
            with pytest.raises(HTTPException) as exc_info:
                create_product(payload, session)
            assert exc_info.value.status_code == 409

    def test_create_db_error(self):
        """Erreur DB leve 500."""
        session = MagicMock()
        session.add = MagicMock()
        session.commit = MagicMock(side_effect=SQLAlchemyError("db error"))
        session.rollback = MagicMock()

        payload = ProductCreate(
            source="amazon",
            reference="ERR",
            url="https://amazon.fr/dp/ERR",
            title="Error",
            currency="EUR",
        )

        with patch("pricewatch.app.api.main.Product"):
            with pytest.raises(HTTPException) as exc_info:
                create_product(payload, session)
            assert exc_info.value.status_code == 500


class TestGetProduct:
    """Tests get_product."""

    def test_get_not_found(self):
        """Produit non trouve leve 404."""
        session = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = None
        session.query.return_value = mock_query

        with pytest.raises(HTTPException) as exc_info:
            get_product(99999, session)
        assert exc_info.value.status_code == 404


class TestUpdateProduct:
    """Tests update_product."""

    def test_update_not_found(self):
        """Update produit non trouve leve 404."""
        session = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = None
        session.query.return_value = mock_query

        payload = ProductUpdate(title="Updated")

        with pytest.raises(HTTPException) as exc_info:
            update_product(99999, payload, session)
        assert exc_info.value.status_code == 404

    def test_update_db_error(self):
        """Erreur DB lors d'update leve 500."""
        session = MagicMock()
        mock_product = MockProduct()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = mock_product
        session.query.return_value = mock_query
        session.commit = MagicMock(side_effect=SQLAlchemyError("error"))
        session.rollback = MagicMock()

        payload = ProductUpdate(title="Updated")

        with pytest.raises(HTTPException) as exc_info:
            update_product(1, payload, session)
        assert exc_info.value.status_code == 500


class TestDeleteProduct:
    """Tests delete_product."""

    def test_delete_not_found(self):
        """Delete produit non trouve leve 404."""
        session = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = None
        session.query.return_value = mock_query

        with pytest.raises(HTTPException) as exc_info:
            delete_product(99999, session)
        assert exc_info.value.status_code == 404

    def test_delete_success(self):
        """Delete produit avec succes."""
        session = MagicMock()
        mock_product = MockProduct()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = mock_product
        session.query.return_value = mock_query
        session.delete = MagicMock()
        session.commit = MagicMock()

        result = delete_product(1, session)
        assert result == {"status": "deleted"}
        session.delete.assert_called_once()

    def test_delete_db_error(self):
        """Erreur DB lors de delete leve 500."""
        session = MagicMock()
        mock_product = MockProduct()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = mock_product
        session.query.return_value = mock_query
        session.delete = MagicMock()
        session.commit = MagicMock(side_effect=SQLAlchemyError("error"))
        session.rollback = MagicMock()

        with pytest.raises(HTTPException) as exc_info:
            delete_product(1, session)
        assert exc_info.value.status_code == 500


class TestCreatePrice:
    """Tests create_price."""

    def test_create_price_db_error(self):
        """Erreur DB lors de creation prix."""
        session = MagicMock()
        session.add = MagicMock()
        session.commit = MagicMock(side_effect=SQLAlchemyError("error"))
        session.rollback = MagicMock()

        payload = PriceHistoryCreate(
            product_id=1,
            price=99.99,
            fetch_method="http",
            fetch_status="success",
            fetched_at=datetime.now(),
        )

        with patch("pricewatch.app.api.main.PriceHistory"):
            with pytest.raises(HTTPException) as exc_info:
                create_price(payload, session)
            assert exc_info.value.status_code == 500


class TestUpdatePrice:
    """Tests update_price."""

    def test_update_price_not_found(self):
        """Update prix non trouve leve 404."""
        session = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = None
        session.query.return_value = mock_query

        payload = PriceHistoryUpdate(price=149.99)

        with pytest.raises(HTTPException) as exc_info:
            update_price(99999, payload, session)
        assert exc_info.value.status_code == 404


class TestDeletePrice:
    """Tests delete_price."""

    def test_delete_price_not_found(self):
        """Delete prix non trouve leve 404."""
        session = MagicMock()
        mock_query = MagicMock()
        mock_query.filter.return_value.one_or_none.return_value = None
        session.query.return_value = mock_query

        with pytest.raises(HTTPException) as exc_info:
            delete_price(99999, session)
        assert exc_info.value.status_code == 404
