# tests/unit/services/test_order_item_exception_service.py
"""Unit tests for OrderItemExceptionService."""

import pytest

from app.modules.catalog.exceptions import ProductNotFoundException
from app.modules.orders.exceptions import (
    ExceptionAlreadyResolvedException,
    OrderItemExceptionNotFoundException,
)
from app.modules.orders.models import OrderItem
from app.modules.orders.services.order_item_exception_service import (
    OrderItemExceptionService,
)


@pytest.fixture
def exception_service():
    """Create an OrderItemExceptionService instance."""
    return OrderItemExceptionService()


@pytest.mark.unit
class TestOrderItemExceptionServiceCreate:
    """Test exception creation."""

    def test_create_exception(
        self, db, exception_service, test_order_item, test_store
    ):
        """Test creating an exception."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
            exception_type="product_not_found",
        )
        db.commit()

        assert exception.id is not None
        assert exception.order_item_id == test_order_item.id
        assert exception.store_id == test_store.id
        assert exception.original_gtin == "4006381333931"
        assert exception.status == "pending"

    def test_create_exception_no_gtin(
        self, db, exception_service, test_order_item, test_store
    ):
        """Test creating an exception without GTIN (e.g., for vouchers)."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin=None,
            original_product_name="Gift Voucher",
            original_sku=None,
            exception_type="product_not_found",
        )
        db.commit()

        assert exception.id is not None
        assert exception.original_gtin is None


@pytest.mark.unit
class TestOrderItemExceptionServiceGet:
    """Test exception retrieval."""

    def test_get_exception_by_id(
        self, db, exception_service, test_order_item, test_store
    ):
        """Test getting an exception by ID."""
        created = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        fetched = exception_service.get_exception_by_id(db, created.id)
        assert fetched.id == created.id
        assert fetched.original_gtin == "4006381333931"

    def test_get_exception_by_id_with_store_filter(
        self, db, exception_service, test_order_item, test_store
    ):
        """Test getting an exception with store filter."""
        created = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        # Should find with correct store
        fetched = exception_service.get_exception_by_id(
            db, created.id, store_id=test_store.id
        )
        assert fetched.id == created.id

        # Should not find with wrong store
        with pytest.raises(OrderItemExceptionNotFoundException):
            exception_service.get_exception_by_id(db, created.id, store_id=99999)

    def test_get_exception_not_found(self, db, exception_service):
        """Test getting a non-existent exception."""
        with pytest.raises(OrderItemExceptionNotFoundException):
            exception_service.get_exception_by_id(db, 99999)

    def test_get_pending_exceptions(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test getting pending exceptions with pagination."""
        # Create multiple order items and exceptions
        for i in range(5):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
            )
            db.add(order_item)  # noqa: PERF006
            db.commit()

            exception_service.create_exception(
                db=db,
                order_item=order_item,
                store_id=test_store.id,
                original_gtin=f"400638133393{i}",
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
        db.commit()

        # Get all
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id
        )
        assert total == 5
        assert len(exceptions) == 5

        # Test pagination
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id, skip=0, limit=2
        )
        assert total == 5
        assert len(exceptions) == 2

    def test_get_pending_exceptions_with_status_filter(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test filtering exceptions by status."""
        # Create order items
        order_items = []
        for i in range(3):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
            )
            db.add(order_item)  # noqa: PERF006
            order_items.append(order_item)
        db.commit()

        # Create exceptions with different statuses
        statuses = ["pending", "resolved", "ignored"]
        for i, status in enumerate(statuses):
            exc = exception_service.create_exception(
                db=db,
                order_item=order_items[i],
                store_id=test_store.id,
                original_gtin=f"400638133393{i}",
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
            exc.status = status
        db.commit()

        # Filter by pending
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id, status="pending"
        )
        assert total == 1
        assert exceptions[0].status == "pending"

    def test_get_pending_exceptions_with_search(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test searching exceptions."""
        order_item = OrderItem(
            order_id=test_order.id,
            product_id=test_product.id,
            product_name="Unique Product",
            product_sku="UNIQUE-SKU",
            quantity=1,
            unit_price=10.00,
            total_price=10.00,
        )
        db.add(order_item)
        db.commit()

        exception_service.create_exception(
            db=db,
            order_item=order_item,
            store_id=test_store.id,
            original_gtin="9876543210123",
            original_product_name="Searchable Product Name",
            original_sku="SEARCH-SKU",
        )
        db.commit()

        # Search by GTIN
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id, search="9876543210123"
        )
        assert total == 1

        # Search by product name
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id, search="Searchable"
        )
        assert total == 1


@pytest.mark.unit
class TestOrderItemExceptionServiceStats:
    """Test exception statistics."""

    def test_get_exception_stats(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test getting exception statistics."""
        # Create order items and exceptions with different statuses
        statuses_to_create = ["pending", "pending", "resolved", "ignored"]
        for i, status in enumerate(statuses_to_create):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
            )
            db.add(order_item)
            db.commit()

            exc = exception_service.create_exception(
                db=db,
                order_item=order_item,
                store_id=test_store.id,
                original_gtin=f"400638133393{i}",
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
            exc.status = status
        db.commit()

        stats = exception_service.get_exception_stats(db, store_id=test_store.id)

        assert stats["pending"] == 2
        assert stats["resolved"] == 1
        assert stats["ignored"] == 1
        assert stats["total"] == 4
        assert stats["orders_with_exceptions"] == 1  # All same order


@pytest.mark.unit
class TestOrderItemExceptionServiceResolve:
    """Test exception resolution."""

    def test_resolve_exception(
        self, db, exception_service, test_order_item, test_store, test_product, test_user
    ):
        """Test resolving an exception."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        resolved = exception_service.resolve_exception(
            db=db,
            exception_id=exception.id,
            product_id=test_product.id,
            resolved_by=test_user.id,
            notes="Matched to existing product",
        )
        db.commit()

        assert resolved.status == "resolved"
        assert resolved.resolved_product_id == test_product.id
        assert resolved.resolved_by == test_user.id
        assert resolved.resolved_at is not None
        assert resolved.resolution_notes == "Matched to existing product"

        # Order item should be updated
        db.refresh(test_order_item)
        assert test_order_item.product_id == test_product.id
        assert test_order_item.needs_product_match is False

    def test_resolve_already_resolved_exception(
        self, db, exception_service, test_order_item, test_store, test_product, test_user
    ):
        """Test that resolving an already resolved exception raises error."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        # Resolve first time
        exception_service.resolve_exception(
            db=db,
            exception_id=exception.id,
            product_id=test_product.id,
            resolved_by=test_user.id,
        )
        db.commit()

        # Try to resolve again
        with pytest.raises(ExceptionAlreadyResolvedException):
            exception_service.resolve_exception(
                db=db,
                exception_id=exception.id,
                product_id=test_product.id,
                resolved_by=test_user.id,
            )

    def test_resolve_with_invalid_product(
        self, db, exception_service, test_order_item, test_store, test_user
    ):
        """Test resolving with non-existent product."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        with pytest.raises(ProductNotFoundException):
            exception_service.resolve_exception(
                db=db,
                exception_id=exception.id,
                product_id=99999,
                resolved_by=test_user.id,
            )

    def test_ignore_exception(
        self, db, exception_service, test_order_item, test_store, test_user
    ):
        """Test ignoring an exception."""
        exception = exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        ignored = exception_service.ignore_exception(
            db=db,
            exception_id=exception.id,
            resolved_by=test_user.id,
            notes="Product discontinued",
        )
        db.commit()

        assert ignored.status == "ignored"
        assert ignored.resolved_by == test_user.id
        assert ignored.resolution_notes == "Product discontinued"


@pytest.mark.unit
class TestOrderItemExceptionServiceAutoMatch:
    """Test auto-matching."""

    def test_auto_match_by_gtin(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test auto-matching exceptions by GTIN."""
        # Set the product GTIN
        test_product.gtin = "4006381333931"
        db.commit()

        # Create order items with exceptions for this GTIN
        for i in range(3):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
                needs_product_match=True,
            )
            db.add(order_item)  # noqa: PERF006
            db.commit()

            exception_service.create_exception(
                db=db,
                order_item=order_item,
                store_id=test_store.id,
                original_gtin="4006381333931",  # Same GTIN
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
        db.commit()

        # Auto-match should resolve all 3
        resolved = exception_service.auto_match_by_gtin(
            db=db,
            store_id=test_store.id,
            gtin="4006381333931",
            product_id=test_product.id,
        )
        db.commit()

        assert len(resolved) == 3
        for exc in resolved:
            assert exc.status == "resolved"
            assert exc.resolved_product_id == test_product.id
            assert "Auto-matched" in exc.resolution_notes

    def test_auto_match_empty_gtin(self, db, exception_service, test_store):
        """Test that empty GTIN returns empty list."""
        resolved = exception_service.auto_match_by_gtin(
            db=db, store_id=test_store.id, gtin="", product_id=1
        )
        assert resolved == []


@pytest.mark.unit
class TestOrderItemExceptionServiceConfirmation:
    """Test confirmation checks."""

    def test_order_has_unresolved_exceptions(
        self, db, exception_service, test_order_item, test_store
    ):
        """Test checking for unresolved exceptions."""
        order_id = test_order_item.order_id

        # Initially no exceptions
        assert exception_service.order_has_unresolved_exceptions(db, order_id) is False

        # Add pending exception
        exception_service.create_exception(
            db=db,
            order_item=test_order_item,
            store_id=test_store.id,
            original_gtin="4006381333931",
            original_product_name="Test Product",
            original_sku="SKU-001",
        )
        db.commit()

        assert exception_service.order_has_unresolved_exceptions(db, order_id) is True

    def test_get_unresolved_exception_count(
        self, db, exception_service, test_order, test_product, test_store
    ):
        """Test getting unresolved exception count."""
        # Create multiple exceptions
        for i in range(3):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
            )
            db.add(order_item)  # noqa: PERF006
            db.commit()

            exception_service.create_exception(
                db=db,
                order_item=order_item,
                store_id=test_store.id,
                original_gtin=f"400638133393{i}",
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
        db.commit()

        count = exception_service.get_unresolved_exception_count(db, test_order.id)
        assert count == 3


@pytest.mark.unit
class TestOrderItemExceptionServiceBulkResolve:
    """Test bulk operations."""

    def test_bulk_resolve_by_gtin(
        self, db, exception_service, test_order, test_product, test_store, test_user
    ):
        """Test bulk resolving exceptions by GTIN."""
        gtin = "4006381333931"

        # Create multiple exceptions for same GTIN
        for i in range(3):
            order_item = OrderItem(
                order_id=test_order.id,
                product_id=test_product.id,
                product_name=f"Product {i}",
                product_sku=f"SKU-{i}",
                quantity=1,
                unit_price=10.00,
                total_price=10.00,
            )
            db.add(order_item)  # noqa: PERF006
            db.commit()

            exception_service.create_exception(
                db=db,
                order_item=order_item,
                store_id=test_store.id,
                original_gtin=gtin,
                original_product_name=f"Product {i}",
                original_sku=f"SKU-{i}",
            )
        db.commit()

        count = exception_service.bulk_resolve_by_gtin(
            db=db,
            store_id=test_store.id,
            gtin=gtin,
            product_id=test_product.id,
            resolved_by=test_user.id,
            notes="Bulk resolved",
        )
        db.commit()

        assert count == 3

        # Verify all are resolved
        exceptions, total = exception_service.get_pending_exceptions(
            db, store_id=test_store.id, status="pending"
        )
        assert total == 0
