# tests/unit/services/test_marketplace_product_service.py
"""
Unit tests for MarketplaceProductService.

Tests cover:
- Product creation with validation
- Product retrieval and filtering
- Product updates
- Product deletion
- Inventory information
- Admin methods
- CSV export
"""

import uuid

import pytest

from app.modules.marketplace.exceptions import (
    InvalidMarketplaceProductDataException,
    MarketplaceProductNotFoundException,
    MarketplaceProductValidationException,
)
from app.modules.marketplace.models import (
    MarketplaceProduct,
    MarketplaceProductTranslation,
)
from app.modules.marketplace.schemas import (
    MarketplaceProductCreate,
    MarketplaceProductUpdate,
)
from app.modules.marketplace.services.marketplace_product_service import (
    MarketplaceProductService,
    marketplace_product_service,
)


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceCreate:
    """Test product creation functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_create_product_success(self, db):
        """Test successful product creation"""
        unique_id = str(uuid.uuid4())[:8]
        product_data = MarketplaceProductCreate(
            marketplace_product_id=f"MP-{unique_id}",
            title="Test Product",
            gtin="1234567890123",
            price="19.99 EUR",
            marketplace="Letzshop",
        )

        product = self.service.create_product(
            db, product_data, title="Test Product", language="en"
        )
        db.commit()

        assert product is not None
        assert product.marketplace_product_id == f"MP-{unique_id}"
        assert product.gtin == "1234567890123"

    def test_create_product_with_translation(self, db):
        """Test product creation with translation"""
        unique_id = str(uuid.uuid4())[:8]
        product_data = MarketplaceProductCreate(
            marketplace_product_id=f"MP-TRANS-{unique_id}",
            title="Test Product Title",
            marketplace="Letzshop",
        )

        product = self.service.create_product(
            db,
            product_data,
            title="Test Product Title",
            description="Test Description",
            language="en",
        )
        db.commit()

        # Check translation was created
        translation = (
            db.query(MarketplaceProductTranslation)
            .filter(
                MarketplaceProductTranslation.marketplace_product_id == product.id,
                MarketplaceProductTranslation.language == "en",
            )
            .first()
        )

        assert translation is not None
        assert translation.title == "Test Product Title"
        assert translation.description == "Test Description"

    def test_create_product_invalid_gtin(self, db):
        """Test product creation fails with invalid GTIN"""
        unique_id = str(uuid.uuid4())[:8]
        product_data = MarketplaceProductCreate(
            marketplace_product_id=f"MP-{unique_id}",
            title="Test Product",
            gtin="invalid-gtin",
            marketplace="Letzshop",
        )

        with pytest.raises(InvalidMarketplaceProductDataException):
            self.service.create_product(db, product_data)

    def test_create_product_empty_id(self, db):
        """Test product creation fails with empty ID"""
        # Note: Pydantic won't allow empty marketplace_product_id, so test the service
        # directly by creating a product and checking validation
        unique_id = str(uuid.uuid4())[:8]
        product_data = MarketplaceProductCreate(
            marketplace_product_id=f"  SPACE-{unique_id}  ",
            title="Test Product",
            marketplace="Letzshop",
        )

        # The service should handle whitespace-only IDs
        product = self.service.create_product(db, product_data)
        db.commit()
        # IDs with only spaces should be stripped to valid IDs
        assert product is not None

    def test_create_product_default_marketplace(self, db):
        """Test product creation uses default marketplace"""
        unique_id = str(uuid.uuid4())[:8]
        product_data = MarketplaceProductCreate(
            marketplace_product_id=f"MP-DEFAULT-{unique_id}",
            title="Test Product",
        )

        product = self.service.create_product(db, product_data)
        db.commit()

        assert product.marketplace == "Letzshop"


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceRetrieval:
    """Test product retrieval functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_get_product_by_id_success(self, db, test_marketplace_product):
        """Test getting product by marketplace ID"""
        product = self.service.get_product_by_id(
            db, test_marketplace_product.marketplace_product_id
        )

        assert product is not None
        assert product.id == test_marketplace_product.id

    def test_get_product_by_id_not_found(self, db):
        """Test getting non-existent product returns None"""
        product = self.service.get_product_by_id(db, "NONEXISTENT")

        assert product is None

    def test_get_product_by_id_or_raise_success(self, db, test_marketplace_product):
        """Test get_product_by_id_or_raise returns product"""
        product = self.service.get_product_by_id_or_raise(
            db, test_marketplace_product.marketplace_product_id
        )

        assert product.id == test_marketplace_product.id

    def test_get_product_by_id_or_raise_not_found(self, db):
        """Test get_product_by_id_or_raise raises exception"""
        with pytest.raises(MarketplaceProductNotFoundException):
            self.service.get_product_by_id_or_raise(db, "NONEXISTENT")

    def test_product_exists_true(self, db, test_marketplace_product):
        """Test product_exists returns True when exists"""
        result = self.service.product_exists(
            db, test_marketplace_product.marketplace_product_id
        )

        assert result is True

    def test_product_exists_false(self, db):
        """Test product_exists returns False when not exists"""
        result = self.service.product_exists(db, "NONEXISTENT")

        assert result is False


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceFiltering:
    """Test product filtering functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_get_products_with_filters_basic(self, db, test_marketplace_product):
        """Test basic product retrieval with filters"""
        products, total = self.service.get_products_with_filters(db)

        assert total >= 1
        assert len(products) >= 1

    def test_get_products_with_brand_filter(self, db, test_marketplace_product):
        """Test product retrieval with brand filter"""
        # Set up a brand
        test_marketplace_product.brand = "TestBrand"
        db.commit()

        products, total = self.service.get_products_with_filters(
            db, brand="TestBrand"
        )

        for product in products:
            assert "testbrand" in (product.brand or "").lower()

    def test_get_products_with_marketplace_filter(self, db, test_marketplace_product):
        """Test product retrieval with marketplace filter"""
        products, total = self.service.get_products_with_filters(
            db, marketplace=test_marketplace_product.marketplace
        )

        for product in products:
            assert test_marketplace_product.marketplace.lower() in (
                product.marketplace or ""
            ).lower()

    def test_get_products_with_search(self, db, test_marketplace_product):
        """Test product retrieval with search"""
        # Create translation with searchable title
        translation = (
            db.query(MarketplaceProductTranslation)
            .filter(
                MarketplaceProductTranslation.marketplace_product_id
                == test_marketplace_product.id
            )
            .first()
        )
        if translation:
            translation.title = "Searchable Test Product"
            db.commit()

            products, total = self.service.get_products_with_filters(
                db, search="Searchable"
            )

            assert total >= 1

    def test_get_products_with_pagination(self, db):
        """Test product retrieval with pagination"""
        products, total = self.service.get_products_with_filters(
            db, skip=0, limit=5
        )

        assert len(products) <= 5


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceUpdate:
    """Test product update functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_update_product_success(self, db, test_marketplace_product):
        """Test successful product update"""
        update_data = MarketplaceProductUpdate(brand="UpdatedBrand")

        updated = self.service.update_product(
            db,
            test_marketplace_product.marketplace_product_id,
            update_data,
        )
        db.commit()

        assert updated.brand == "UpdatedBrand"

    def test_update_product_with_translation(self, db, test_marketplace_product):
        """Test product update with translation"""
        update_data = MarketplaceProductUpdate()

        updated = self.service.update_product(
            db,
            test_marketplace_product.marketplace_product_id,
            update_data,
            title="Updated Title",
            description="Updated Description",
            language="en",
        )
        db.commit()

        # Verify translation
        title = updated.get_title("en")
        assert title == "Updated Title"

    def test_update_product_not_found(self, db):
        """Test update raises for non-existent product"""
        update_data = MarketplaceProductUpdate(brand="NewBrand")

        with pytest.raises(MarketplaceProductNotFoundException):
            self.service.update_product(db, "NONEXISTENT", update_data)

    def test_update_product_invalid_gtin(self, db, test_marketplace_product):
        """Test update fails with invalid GTIN"""
        update_data = MarketplaceProductUpdate(gtin="invalid")

        with pytest.raises(InvalidMarketplaceProductDataException):
            self.service.update_product(
                db,
                test_marketplace_product.marketplace_product_id,
                update_data,
            )


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceDelete:
    """Test product deletion functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_delete_product_success(self, db):
        """Test successful product deletion"""
        # Create a product to delete
        unique_id = str(uuid.uuid4())[:8]
        product = MarketplaceProduct(
            marketplace_product_id=f"DELETE-{unique_id}",
            marketplace="Letzshop",
        )
        db.add(product)
        db.commit()

        result = self.service.delete_product(db, f"DELETE-{unique_id}")
        db.commit()

        assert result is True

        # Verify deleted
        deleted = self.service.get_product_by_id(db, f"DELETE-{unique_id}")
        assert deleted is None

    def test_delete_product_not_found(self, db):
        """Test delete raises for non-existent product"""
        with pytest.raises(MarketplaceProductNotFoundException):
            self.service.delete_product(db, "NONEXISTENT")


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceInventory:
    """Test inventory functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_get_inventory_info_not_found(self, db):
        """Test get_inventory_info returns None when not found"""
        result = self.service.get_inventory_info(db, "NONEXISTENT_GTIN")

        assert result is None

    def test_get_inventory_info_with_inventory(self, db, test_inventory):
        """Test get_inventory_info returns data when exists"""
        gtin = test_inventory.gtin
        if gtin:
            result = self.service.get_inventory_info(db, gtin)

            if result:
                assert result.gtin == gtin
                assert result.total_quantity >= 0


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceAdmin:
    """Test admin functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_get_admin_products(self, db, test_marketplace_product):
        """Test admin product listing"""
        products, total = self.service.get_admin_products(db)

        assert total >= 1
        assert len(products) >= 1

    def test_get_admin_products_with_search(self, db, test_marketplace_product):
        """Test admin product listing with search"""
        products, total = self.service.get_admin_products(
            db, search=test_marketplace_product.marketplace_product_id[:5]
        )

        # Should find at least our test product
        assert total >= 0

    def test_get_admin_products_with_filters(self, db, test_marketplace_product):
        """Test admin product listing with filters"""
        products, total = self.service.get_admin_products(
            db,
            marketplace=test_marketplace_product.marketplace,
            is_active=True,
        )

        for product in products:
            assert product["is_active"] is True

    def test_get_admin_product_stats(self, db, test_marketplace_product):
        """Test admin product statistics"""
        stats = self.service.get_admin_product_stats(db)

        assert "total" in stats
        assert "active" in stats
        assert "inactive" in stats
        assert "by_marketplace" in stats
        assert stats["total"] >= 1

    def test_get_admin_product_stats_with_filters(self, db, test_marketplace_product):
        """Test admin product statistics with filters"""
        stats = self.service.get_admin_product_stats(
            db, marketplace=test_marketplace_product.marketplace
        )

        assert stats["total"] >= 0

    def test_get_marketplaces_list(self, db, test_marketplace_product):
        """Test getting unique marketplaces list"""
        marketplaces = self.service.get_marketplaces_list(db)

        assert isinstance(marketplaces, list)
        if test_marketplace_product.marketplace:
            assert test_marketplace_product.marketplace in marketplaces

    def test_get_source_stores_list(self, db, test_marketplace_product):
        """Test getting unique store names list"""
        stores = self.service.get_source_stores_list(db)

        assert isinstance(stores, list)

    def test_get_admin_product_detail(self, db, test_marketplace_product):
        """Test getting detailed product info for admin"""
        detail = self.service.get_admin_product_detail(db, test_marketplace_product.id)

        assert detail["id"] == test_marketplace_product.id
        assert detail["marketplace_product_id"] == test_marketplace_product.marketplace_product_id
        assert "translations" in detail

    def test_get_admin_product_detail_not_found(self, db):
        """Test admin product detail raises for non-existent"""
        with pytest.raises(MarketplaceProductNotFoundException):
            self.service.get_admin_product_detail(db, 99999)


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceCsvExport:
    """Test CSV export functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_generate_csv_export_header(self, db):
        """Test CSV export generates header"""
        csv_generator = self.service.generate_csv_export(db)
        header = next(csv_generator)

        assert "marketplace_product_id" in header
        assert "title" in header
        assert "price" in header

    def test_generate_csv_export_with_data(self, db, test_marketplace_product):
        """Test CSV export generates data rows"""
        rows = list(self.service.generate_csv_export(db))

        # Should have header + at least one data row
        assert len(rows) >= 1


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceCopyToCatalog:
    """Test copy to store catalog functionality"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_copy_to_store_catalog_success(
        self, db, test_marketplace_product, test_store
    ):
        """Test copying products to store catalog"""
        from unittest.mock import MagicMock, patch

        # Create a mock subscription
        mock_subscription = MagicMock()
        mock_subscription.products_limit = 100

        with patch(
            "app.modules.billing.services.subscription_service.subscription_service"
        ) as mock_sub:
            mock_sub.get_or_create_subscription.return_value = mock_subscription

            result = self.service.copy_to_store_catalog(
                db,
                [test_marketplace_product.id],
                test_store.id,
            )
            db.commit()

        assert "copied" in result
        assert "skipped" in result
        assert "failed" in result

    def test_copy_to_store_catalog_store_not_found(self, db, test_marketplace_product):
        """Test copy fails for non-existent store"""
        from app.modules.tenancy.exceptions import StoreNotFoundException

        with pytest.raises(StoreNotFoundException):
            self.service.copy_to_store_catalog(
                db,
                [test_marketplace_product.id],
                99999,
            )

    def test_copy_to_store_catalog_no_products(self, db, test_store):
        """Test copy fails when no products found"""
        with pytest.raises(MarketplaceProductNotFoundException):
            self.service.copy_to_store_catalog(
                db,
                [99999],  # Non-existent product
                test_store.id,
            )


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceHelpers:
    """Test helper methods"""

    def setup_method(self):
        self.service = MarketplaceProductService()

    def test_validate_product_data_missing_id(self):
        """Test validation fails for missing marketplace_product_id"""
        with pytest.raises(MarketplaceProductValidationException):
            self.service._validate_product_data({})

    def test_validate_product_data_success(self):
        """Test validation passes with required fields"""
        # Should not raise
        self.service._validate_product_data(
            {"marketplace_product_id": "TEST-123"}
        )

    def test_normalize_product_data(self):
        """Test product data normalization"""
        data = {
            "marketplace_product_id": "  TEST-123  ",
            "brand": "  TestBrand  ",
            "marketplace": "  Letzshop  ",
        }

        normalized = self.service._normalize_product_data(data)

        assert normalized["marketplace_product_id"] == "TEST-123"
        assert normalized["brand"] == "TestBrand"
        assert normalized["marketplace"] == "Letzshop"


@pytest.mark.unit
@pytest.mark.service
class TestMarketplaceProductServiceSingleton:
    """Test singleton instance"""

    def test_singleton_exists(self):
        """Test marketplace_product_service singleton exists"""
        assert marketplace_product_service is not None
        assert isinstance(marketplace_product_service, MarketplaceProductService)
