# tests/unit/services/test_invoice_service.py
"""Unit tests for InvoiceService."""

from decimal import Decimal

import pytest

from app.modules.orders.exceptions import (
    InvalidInvoiceStatusTransitionException,
    InvoiceNotFoundException,
    InvoiceSettingsNotFoundException,
    InvoiceValidationException,
)
from app.modules.orders.models import (
    Invoice,
    InvoiceStatus,
    StoreInvoiceSettings,
    VATRegime,
)
from app.modules.orders.schemas import (
    StoreInvoiceSettingsCreate,
    StoreInvoiceSettingsUpdate,
)
from app.modules.orders.services.invoice_service import (
    InvoiceService,
)


@pytest.mark.unit
@pytest.mark.invoice
class TestInvoiceServiceVATCalculation:
    """Test suite for InvoiceService VAT calculation methods."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = InvoiceService()

    # ==================== VAT Rate Lookup Tests ====================

    def test_get_vat_rate_for_luxembourg(self):
        """Test Luxembourg VAT rate is 17%."""
        rate = self.service.get_vat_rate_for_country("LU")
        assert rate == Decimal("17.00")

    def test_get_vat_rate_for_germany(self):
        """Test Germany VAT rate is 19%."""
        rate = self.service.get_vat_rate_for_country("DE")
        assert rate == Decimal("19.00")

    def test_get_vat_rate_for_france(self):
        """Test France VAT rate is 20%."""
        rate = self.service.get_vat_rate_for_country("FR")
        assert rate == Decimal("20.00")

    def test_get_vat_rate_for_non_eu_country(self):
        """Test non-EU country returns 0% VAT."""
        rate = self.service.get_vat_rate_for_country("US")
        assert rate == Decimal("0.00")

    def test_get_vat_rate_lowercase_country(self):
        """Test VAT rate lookup works with lowercase country codes."""
        rate = self.service.get_vat_rate_for_country("de")
        assert rate == Decimal("19.00")

    # ==================== VAT Rate Label Tests ====================

    def test_get_vat_rate_label_luxembourg(self):
        """Test VAT rate label for Luxembourg."""
        label = self.service.get_vat_rate_label("LU", Decimal("17.00"))
        assert "Luxembourg" in label
        assert "17" in label

    def test_get_vat_rate_label_germany(self):
        """Test VAT rate label for Germany."""
        label = self.service.get_vat_rate_label("DE", Decimal("19.00"))
        assert "Germany" in label
        assert "19" in label

    # ==================== VAT Regime Determination Tests ====================

    def test_determine_vat_regime_domestic(self):
        """Test domestic sales (same country) use domestic VAT."""
        regime, rate, dest = self.service.determine_vat_regime(
            seller_country="LU",
            buyer_country="LU",
            buyer_vat_number=None,
            seller_oss_registered=False,
        )
        assert regime == VATRegime.DOMESTIC
        assert rate == Decimal("17.00")
        assert dest is None

    def test_determine_vat_regime_reverse_charge(self):
        """Test B2B with valid VAT number uses reverse charge."""
        regime, rate, dest = self.service.determine_vat_regime(
            seller_country="LU",
            buyer_country="DE",
            buyer_vat_number="DE123456789",
            seller_oss_registered=False,
        )
        assert regime == VATRegime.REVERSE_CHARGE
        assert rate == Decimal("0.00")
        assert dest == "DE"

    def test_determine_vat_regime_oss_registered(self):
        """Test B2C cross-border with OSS uses destination VAT."""
        regime, rate, dest = self.service.determine_vat_regime(
            seller_country="LU",
            buyer_country="DE",
            buyer_vat_number=None,
            seller_oss_registered=True,
        )
        assert regime == VATRegime.OSS
        assert rate == Decimal("19.00")  # German VAT
        assert dest == "DE"

    def test_determine_vat_regime_no_oss(self):
        """Test B2C cross-border without OSS uses origin VAT."""
        regime, rate, dest = self.service.determine_vat_regime(
            seller_country="LU",
            buyer_country="DE",
            buyer_vat_number=None,
            seller_oss_registered=False,
        )
        assert regime == VATRegime.ORIGIN
        assert rate == Decimal("17.00")  # Luxembourg VAT
        assert dest == "DE"

    def test_determine_vat_regime_non_eu_exempt(self):
        """Test non-EU sales are VAT exempt."""
        regime, rate, dest = self.service.determine_vat_regime(
            seller_country="LU",
            buyer_country="US",
            buyer_vat_number=None,
            seller_oss_registered=True,
        )
        assert regime == VATRegime.EXEMPT
        assert rate == Decimal("0.00")
        assert dest == "US"


@pytest.mark.unit
@pytest.mark.invoice
class TestInvoiceServiceSettings:
    """Test suite for InvoiceService settings management."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = InvoiceService()

    # ==================== Get Settings Tests ====================

    def test_get_settings_not_found(self, db, test_store):
        """Test getting settings for store without settings returns None."""
        settings = self.service.get_settings(db, test_store.id)
        assert settings is None

    def test_get_settings_or_raise_not_found(self, db, test_store):
        """Test get_settings_or_raise raises when settings don't exist."""
        with pytest.raises(InvoiceSettingsNotFoundException):
            self.service.get_settings_or_raise(db, test_store.id)

    # ==================== Create Settings Tests ====================

    def test_create_settings_success(self, db, test_store):
        """Test creating invoice settings successfully."""
        data = StoreInvoiceSettingsCreate(
            merchant_name="Test Merchant S.A.",
            merchant_address="123 Test Street",
            merchant_city="Luxembourg",
            merchant_postal_code="L-1234",
            merchant_country="LU",
            vat_number="LU12345678",
        )

        settings = self.service.create_settings(db, test_store.id, data)

        assert settings.store_id == test_store.id
        assert settings.merchant_name == "Test Merchant S.A."
        assert settings.merchant_country == "LU"
        assert settings.vat_number == "LU12345678"
        assert settings.invoice_prefix == "INV"
        assert settings.invoice_next_number == 1

    def test_create_settings_with_custom_prefix(self, db, test_store):
        """Test creating settings with custom invoice prefix."""
        data = StoreInvoiceSettingsCreate(
            merchant_name="Custom Prefix Merchant",
            invoice_prefix="FAC",
            invoice_number_padding=6,
        )

        settings = self.service.create_settings(db, test_store.id, data)

        assert settings.invoice_prefix == "FAC"
        assert settings.invoice_number_padding == 6

    def test_create_settings_duplicate_raises(self, db, test_store):
        """Test creating duplicate settings raises ValidationException."""
        data = StoreInvoiceSettingsCreate(merchant_name="First Settings")
        self.service.create_settings(db, test_store.id, data)

        with pytest.raises(InvoiceValidationException) as exc_info:
            self.service.create_settings(db, test_store.id, data)

        assert "already exist" in str(exc_info.value)

    # ==================== Update Settings Tests ====================

    def test_update_settings_success(self, db, test_store):
        """Test updating invoice settings."""
        # Create initial settings
        create_data = StoreInvoiceSettingsCreate(
            merchant_name="Original Merchant"
        )
        self.service.create_settings(db, test_store.id, create_data)

        # Update settings
        update_data = StoreInvoiceSettingsUpdate(
            merchant_name="Updated Merchant",
            bank_iban="LU123456789012345678",
        )
        settings = self.service.update_settings(db, test_store.id, update_data)

        assert settings.merchant_name == "Updated Merchant"
        assert settings.bank_iban == "LU123456789012345678"

    def test_update_settings_not_found(self, db, test_store):
        """Test updating non-existent settings raises exception."""
        update_data = StoreInvoiceSettingsUpdate(merchant_name="Updated")

        with pytest.raises(InvoiceSettingsNotFoundException):
            self.service.update_settings(db, test_store.id, update_data)

    # ==================== Invoice Number Generation Tests ====================

    def test_get_next_invoice_number(self, db, test_store):
        """Test invoice number generation and increment."""
        create_data = StoreInvoiceSettingsCreate(
            merchant_name="Test Merchant",
            invoice_prefix="INV",
            invoice_number_padding=5,
        )
        settings = self.service.create_settings(db, test_store.id, create_data)

        # Generate first invoice number
        num1 = self.service._get_next_invoice_number(db, settings)
        assert num1 == "INV00001"
        assert settings.invoice_next_number == 2

        # Generate second invoice number
        num2 = self.service._get_next_invoice_number(db, settings)
        assert num2 == "INV00002"
        assert settings.invoice_next_number == 3


@pytest.mark.unit
@pytest.mark.invoice
class TestInvoiceServiceCRUD:
    """Test suite for InvoiceService CRUD operations."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = InvoiceService()

    # ==================== Get Invoice Tests ====================

    def test_get_invoice_not_found(self, db, test_store):
        """Test getting non-existent invoice returns None."""
        invoice = self.service.get_invoice(db, test_store.id, 99999)
        assert invoice is None

    def test_get_invoice_or_raise_not_found(self, db, test_store):
        """Test get_invoice_or_raise raises for non-existent invoice."""
        with pytest.raises(InvoiceNotFoundException):
            self.service.get_invoice_or_raise(db, test_store.id, 99999)

    def test_get_invoice_wrong_store(self, db, test_store, test_invoice_settings):
        """Test cannot get invoice from different store."""
        # Create an invoice
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        # Try to get with different store ID
        result = self.service.get_invoice(db, 99999, invoice.id)
        assert result is None

    # ==================== List Invoices Tests ====================

    def test_list_invoices_empty(self, db, test_store):
        """Test listing invoices when none exist."""
        invoices, total = self.service.list_invoices(db, test_store.id)
        assert invoices == []
        assert total == 0

    def test_list_invoices_with_status_filter(
        self, db, test_store, test_invoice_settings
    ):
        """Test listing invoices filtered by status."""
        # Create invoices with different statuses
        for status in [InvoiceStatus.DRAFT, InvoiceStatus.ISSUED, InvoiceStatus.PAID]:
            invoice = Invoice(
                store_id=test_store.id,
                invoice_number=f"INV-{status.value}",
                invoice_date=test_invoice_settings.created_at,
                status=status.value,
                seller_details={"merchant_name": "Test"},
                buyer_details={"name": "Buyer"},
                line_items=[],
                vat_rate=Decimal("17.00"),
                subtotal_cents=10000,
                vat_amount_cents=1700,
                total_cents=11700,
            )
            db.add(invoice)  # noqa: PERF006
        db.commit()

        # Filter by draft
        drafts, total = self.service.list_invoices(
            db, test_store.id, status="draft"
        )
        assert total == 1
        assert all(inv.status == "draft" for inv in drafts)

    def test_list_invoices_pagination(self, db, test_store, test_invoice_settings):
        """Test invoice listing pagination."""
        # Create 5 invoices
        for i in range(5):
            invoice = Invoice(
                store_id=test_store.id,
                invoice_number=f"INV0000{i+1}",
                invoice_date=test_invoice_settings.created_at,
                seller_details={"merchant_name": "Test"},
                buyer_details={"name": "Buyer"},
                line_items=[],
                vat_rate=Decimal("17.00"),
                subtotal_cents=10000,
                vat_amount_cents=1700,
                total_cents=11700,
            )
            db.add(invoice)  # noqa: PERF006
        db.commit()

        # Get first page
        page1, total = self.service.list_invoices(
            db, test_store.id, page=1, per_page=2
        )
        assert len(page1) == 2
        assert total == 5

        # Get second page
        page2, _ = self.service.list_invoices(
            db, test_store.id, page=2, per_page=2
        )
        assert len(page2) == 2


@pytest.mark.unit
@pytest.mark.invoice
class TestInvoiceServiceStatusManagement:
    """Test suite for InvoiceService status management."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = InvoiceService()

    def test_update_status_draft_to_issued(
        self, db, test_store, test_invoice_settings
    ):
        """Test updating invoice status from draft to issued."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.DRAFT.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        updated = self.service.update_status(
            db, test_store.id, invoice.id, "issued"
        )

        assert updated.status == "issued"

    def test_update_status_issued_to_paid(
        self, db, test_store, test_invoice_settings
    ):
        """Test updating invoice status from issued to paid."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.ISSUED.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        updated = self.service.update_status(
            db, test_store.id, invoice.id, "paid"
        )

        assert updated.status == "paid"

    def test_update_status_cancelled_cannot_change(
        self, db, test_store, test_invoice_settings
    ):
        """Test that cancelled invoices cannot have status changed."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.CANCELLED.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        with pytest.raises(InvalidInvoiceStatusTransitionException) as exc_info:
            self.service.update_status(db, test_store.id, invoice.id, "issued")

        assert "cancelled" in str(exc_info.value).lower()

    def test_update_status_invalid_status(
        self, db, test_store, test_invoice_settings
    ):
        """Test updating with invalid status raises ValidationException."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.DRAFT.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        with pytest.raises(InvalidInvoiceStatusTransitionException) as exc_info:
            self.service.update_status(
                db, test_store.id, invoice.id, "invalid_status"
            )

        assert "Invalid status" in str(exc_info.value)

    def test_mark_as_issued(self, db, test_store, test_invoice_settings):
        """Test mark_as_issued helper method."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.DRAFT.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        updated = self.service.mark_as_issued(db, test_store.id, invoice.id)
        assert updated.status == InvoiceStatus.ISSUED.value

    def test_mark_as_paid(self, db, test_store, test_invoice_settings):
        """Test mark_as_paid helper method."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.ISSUED.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        updated = self.service.mark_as_paid(db, test_store.id, invoice.id)
        assert updated.status == InvoiceStatus.PAID.value

    def test_cancel_invoice(self, db, test_store, test_invoice_settings):
        """Test cancel_invoice helper method."""
        invoice = Invoice(
            store_id=test_store.id,
            invoice_number="INV00001",
            invoice_date=test_invoice_settings.created_at,
            status=InvoiceStatus.DRAFT.value,
            seller_details={"merchant_name": "Test"},
            buyer_details={"name": "Buyer"},
            line_items=[],
            vat_rate=Decimal("17.00"),
            subtotal_cents=10000,
            vat_amount_cents=1700,
            total_cents=11700,
        )
        db.add(invoice)
        db.commit()

        updated = self.service.cancel_invoice(db, test_store.id, invoice.id)
        assert updated.status == InvoiceStatus.CANCELLED.value


@pytest.mark.unit
@pytest.mark.invoice
class TestInvoiceServiceStatistics:
    """Test suite for InvoiceService statistics."""

    def setup_method(self):
        """Initialize service instance before each test."""
        self.service = InvoiceService()

    def test_get_invoice_stats_empty(self, db, test_store):
        """Test stats when no invoices exist."""
        stats = self.service.get_invoice_stats(db, test_store.id)

        assert stats["total_invoices"] == 0
        assert stats["total_revenue_cents"] == 0
        assert stats["draft_count"] == 0
        assert stats["paid_count"] == 0

    def test_get_invoice_stats_with_invoices(
        self, db, test_store, test_invoice_settings
    ):
        """Test stats calculation with multiple invoices."""
        # Create invoices
        statuses = [
            (InvoiceStatus.DRAFT.value, 10000),
            (InvoiceStatus.ISSUED.value, 20000),
            (InvoiceStatus.PAID.value, 30000),
            (InvoiceStatus.CANCELLED.value, 5000),
        ]

        for i, (status, total) in enumerate(statuses):
            invoice = Invoice(
                store_id=test_store.id,
                invoice_number=f"INV0000{i+1}",
                invoice_date=test_invoice_settings.created_at,
                status=status,
                seller_details={"merchant_name": "Test"},
                buyer_details={"name": "Buyer"},
                line_items=[],
                vat_rate=Decimal("17.00"),
                subtotal_cents=total,
                vat_amount_cents=int(total * 0.17),
                total_cents=total + int(total * 0.17),
            )
            db.add(invoice)
        db.commit()

        stats = self.service.get_invoice_stats(db, test_store.id)

        assert stats["total_invoices"] == 4
        # Revenue only counts issued and paid
        expected_revenue = 20000 + int(20000 * 0.17) + 30000 + int(30000 * 0.17)
        assert stats["total_revenue_cents"] == expected_revenue
        assert stats["draft_count"] == 1
        assert stats["paid_count"] == 1


# ==================== Fixtures ====================

@pytest.fixture
def test_invoice_settings(db, test_store):
    """Create test invoice settings."""
    settings = StoreInvoiceSettings(
        store_id=test_store.id,
        merchant_name="Test Invoice Merchant",
        merchant_country="LU",
        invoice_prefix="INV",
        invoice_next_number=1,
        invoice_number_padding=5,
    )
    db.add(settings)
    db.commit()
    db.refresh(settings)
    return settings
