# app/modules/orders/services/invoice_service.py
"""
Invoice service for generating and managing invoices.

Handles:
- Store invoice settings management
- Invoice generation from orders
- VAT calculation (Luxembourg, EU, B2B reverse charge)
- Invoice number sequencing
- PDF generation (via separate module)
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime
from decimal import Decimal
from typing import TYPE_CHECKING, Any

from sqlalchemy import and_, func
from sqlalchemy.orm import Session

from app.modules.orders.exceptions import (
    InvalidInvoiceStatusTransitionException,
    InvoiceNotFoundException,
    InvoiceSettingsNotFoundException,
    InvoiceValidationException,
    OrderNotFoundException,
)
from app.modules.orders.models.invoice import (
    Invoice,
    InvoiceStatus,
    StoreInvoiceSettings,
    VATRegime,
)
from app.modules.orders.models.order import Order
from app.modules.orders.schemas.invoice import (
    StoreInvoiceSettingsCreate,
    StoreInvoiceSettingsUpdate,
)

if TYPE_CHECKING:
    from app.modules.tenancy.models import Store

logger = logging.getLogger(__name__)


# EU VAT rates by country code (2024 standard rates)
EU_VAT_RATES: dict[str, Decimal] = {
    "AT": Decimal("20.00"),
    "BE": Decimal("21.00"),
    "BG": Decimal("20.00"),
    "HR": Decimal("25.00"),
    "CY": Decimal("19.00"),
    "CZ": Decimal("21.00"),
    "DK": Decimal("25.00"),
    "EE": Decimal("22.00"),
    "FI": Decimal("24.00"),
    "FR": Decimal("20.00"),
    "DE": Decimal("19.00"),
    "GR": Decimal("24.00"),
    "HU": Decimal("27.00"),
    "IE": Decimal("23.00"),
    "IT": Decimal("22.00"),
    "LV": Decimal("21.00"),
    "LT": Decimal("21.00"),
    "LU": Decimal("17.00"),
    "MT": Decimal("18.00"),
    "NL": Decimal("21.00"),
    "PL": Decimal("23.00"),
    "PT": Decimal("23.00"),
    "RO": Decimal("19.00"),
    "SK": Decimal("20.00"),
    "SI": Decimal("22.00"),
    "ES": Decimal("21.00"),
    "SE": Decimal("25.00"),
}

LU_VAT_RATES = {
    "standard": Decimal("17.00"),
    "intermediate": Decimal("14.00"),
    "reduced": Decimal("8.00"),
    "super_reduced": Decimal("3.00"),
}


class InvoiceService:
    """Service for invoice operations."""

    # =========================================================================
    # VAT Calculation
    # =========================================================================

    def get_vat_rate_for_country(self, country_iso: str) -> Decimal:
        """Get standard VAT rate for EU country."""
        return EU_VAT_RATES.get(country_iso.upper(), Decimal("0.00"))

    def get_vat_rate_label(self, country_iso: str, vat_rate: Decimal) -> str:
        """Get human-readable VAT rate label."""
        country_names = {
            "AT": "Austria", "BE": "Belgium", "BG": "Bulgaria", "HR": "Croatia",
            "CY": "Cyprus", "CZ": "Czech Republic", "DK": "Denmark", "EE": "Estonia",
            "FI": "Finland", "FR": "France", "DE": "Germany", "GR": "Greece",
            "HU": "Hungary", "IE": "Ireland", "IT": "Italy", "LV": "Latvia",
            "LT": "Lithuania", "LU": "Luxembourg", "MT": "Malta", "NL": "Netherlands",
            "PL": "Poland", "PT": "Portugal", "RO": "Romania", "SK": "Slovakia",
            "SI": "Slovenia", "ES": "Spain", "SE": "Sweden",
        }
        country_name = country_names.get(country_iso.upper(), country_iso)
        return f"{country_name} VAT {vat_rate}%"

    def determine_vat_regime(
        self,
        seller_country: str,
        buyer_country: str,
        buyer_vat_number: str | None,
        seller_oss_registered: bool,
    ) -> tuple[VATRegime, Decimal, str | None]:
        """Determine VAT regime and rate for invoice."""
        seller_country = seller_country.upper()
        buyer_country = buyer_country.upper()

        if seller_country == buyer_country:
            vat_rate = self.get_vat_rate_for_country(seller_country)
            return VATRegime.DOMESTIC, vat_rate, None

        if buyer_country in EU_VAT_RATES:
            if buyer_vat_number:
                return VATRegime.REVERSE_CHARGE, Decimal("0.00"), buyer_country

            if seller_oss_registered:
                vat_rate = self.get_vat_rate_for_country(buyer_country)
                return VATRegime.OSS, vat_rate, buyer_country
            vat_rate = self.get_vat_rate_for_country(seller_country)
            return VATRegime.ORIGIN, vat_rate, buyer_country

        return VATRegime.EXEMPT, Decimal("0.00"), buyer_country

    # =========================================================================
    # Invoice Settings Management
    # =========================================================================

    def get_settings(
        self, db: Session, store_id: int
    ) -> StoreInvoiceSettings | None:
        """Get store invoice settings."""
        return (
            db.query(StoreInvoiceSettings)
            .filter(StoreInvoiceSettings.store_id == store_id)
            .first()
        )

    def get_settings_or_raise(
        self, db: Session, store_id: int
    ) -> StoreInvoiceSettings:
        """Get store invoice settings or raise exception."""
        settings = self.get_settings(db, store_id)
        if not settings:
            raise InvoiceSettingsNotFoundException(store_id)
        return settings

    def create_settings(
        self,
        db: Session,
        store_id: int,
        data: StoreInvoiceSettingsCreate,
    ) -> StoreInvoiceSettings:
        """Create store invoice settings."""
        existing = self.get_settings(db, store_id)
        if existing:
            raise InvoiceValidationException(
                "Invoice settings already exist for this store"
            )

        settings = StoreInvoiceSettings(
            store_id=store_id,
            **data.model_dump(),
        )
        db.add(settings)
        db.flush()
        db.refresh(settings)

        logger.info(f"Created invoice settings for store {store_id}")
        return settings

    def update_settings(
        self,
        db: Session,
        store_id: int,
        data: StoreInvoiceSettingsUpdate,
    ) -> StoreInvoiceSettings:
        """Update store invoice settings."""
        settings = self.get_settings_or_raise(db, store_id)

        update_data = data.model_dump(exclude_unset=True)
        for key, value in update_data.items():
            setattr(settings, key, value)

        settings.updated_at = datetime.now(UTC)
        db.flush()
        db.refresh(settings)

        logger.info(f"Updated invoice settings for store {store_id}")
        return settings

    def create_settings_from_store(
        self,
        db: Session,
        store: Store,
    ) -> StoreInvoiceSettings:
        """Create invoice settings from store/merchant info."""
        merchant = store.merchant

        settings = StoreInvoiceSettings(
            store_id=store.id,
            merchant_name=merchant.legal_name if merchant else store.name,
            merchant_address=store.effective_business_address,
            merchant_city=None,
            merchant_postal_code=None,
            merchant_country="LU",
            vat_number=store.effective_tax_number,
            is_vat_registered=bool(store.effective_tax_number),
        )
        db.add(settings)
        db.flush()
        db.refresh(settings)

        logger.info(f"Created invoice settings from store data for store {store.id}")
        return settings

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

    def _get_next_invoice_number(
        self, db: Session, settings: StoreInvoiceSettings
    ) -> str:
        """Generate next invoice number and increment counter."""
        number = str(settings.invoice_next_number).zfill(settings.invoice_number_padding)
        invoice_number = f"{settings.invoice_prefix}{number}"

        settings.invoice_next_number += 1
        db.flush()

        return invoice_number

    # =========================================================================
    # Invoice Creation
    # =========================================================================

    def create_invoice_from_order(
        self,
        db: Session,
        store_id: int,
        order_id: int,
        notes: str | None = None,
    ) -> Invoice:
        """Create an invoice from an order."""
        settings = self.get_settings_or_raise(db, store_id)

        order = (
            db.query(Order)
            .filter(and_(Order.id == order_id, Order.store_id == store_id))
            .first()
        )
        if not order:
            raise OrderNotFoundException(f"Order {order_id} not found")

        existing = (
            db.query(Invoice)
            .filter(and_(Invoice.order_id == order_id, Invoice.store_id == store_id))
            .first()
        )
        if existing:
            raise InvoiceValidationException(f"Invoice already exists for order {order_id}")

        buyer_country = order.bill_country_iso
        vat_regime, vat_rate, destination_country = self.determine_vat_regime(
            seller_country=settings.merchant_country,
            buyer_country=buyer_country,
            buyer_vat_number=None,
            seller_oss_registered=settings.is_oss_registered,
        )

        seller_details = {
            "merchant_name": settings.merchant_name,
            "address": settings.merchant_address,
            "city": settings.merchant_city,
            "postal_code": settings.merchant_postal_code,
            "country": settings.merchant_country,
            "vat_number": settings.vat_number,
        }

        buyer_details = {
            "name": f"{order.bill_first_name} {order.bill_last_name}".strip(),
            "email": order.customer_email,
            "address": order.bill_address_line_1,
            "city": order.bill_city,
            "postal_code": order.bill_postal_code,
            "country": order.bill_country_iso,
            "vat_number": None,
        }
        if order.bill_company:
            buyer_details["company"] = order.bill_company

        line_items = []
        for item in order.items:
            line_items.append({
                "description": item.product_name,
                "quantity": item.quantity,
                "unit_price_cents": item.unit_price_cents,
                "total_cents": item.total_price_cents,
                "sku": item.product_sku,
                "ean": item.gtin,
            })

        subtotal_cents = sum(item["total_cents"] for item in line_items)

        if vat_rate > 0:
            vat_amount_cents = int(subtotal_cents * float(vat_rate) / 100)
        else:
            vat_amount_cents = 0

        total_cents = subtotal_cents + vat_amount_cents

        vat_rate_label = None
        if vat_rate > 0:
            if destination_country:
                vat_rate_label = self.get_vat_rate_label(destination_country, vat_rate)
            else:
                vat_rate_label = self.get_vat_rate_label(settings.merchant_country, vat_rate)

        invoice_number = self._get_next_invoice_number(db, settings)

        invoice = Invoice(
            store_id=store_id,
            order_id=order_id,
            invoice_number=invoice_number,
            invoice_date=datetime.now(UTC),
            status=InvoiceStatus.DRAFT.value,
            seller_details=seller_details,
            buyer_details=buyer_details,
            line_items=line_items,
            vat_regime=vat_regime.value,
            destination_country=destination_country,
            vat_rate=vat_rate,
            vat_rate_label=vat_rate_label,
            currency=order.currency,
            subtotal_cents=subtotal_cents,
            vat_amount_cents=vat_amount_cents,
            total_cents=total_cents,
            payment_terms=settings.payment_terms,
            bank_details={
                "bank_name": settings.bank_name,
                "iban": settings.bank_iban,
                "bic": settings.bank_bic,
            } if settings.bank_iban else None,
            footer_text=settings.footer_text,
            notes=notes,
        )

        db.add(invoice)
        db.flush()
        db.refresh(invoice)

        logger.info(
            f"Created invoice {invoice_number} for order {order_id} "
            f"(store={store_id}, total={total_cents/100:.2f} EUR, VAT={vat_regime.value})"
        )

        return invoice

    # =========================================================================
    # Invoice Retrieval
    # =========================================================================

    def get_invoice(
        self, db: Session, store_id: int, invoice_id: int
    ) -> Invoice | None:
        """Get invoice by ID."""
        return (
            db.query(Invoice)
            .filter(and_(Invoice.id == invoice_id, Invoice.store_id == store_id))
            .first()
        )

    def get_invoice_or_raise(
        self, db: Session, store_id: int, invoice_id: int
    ) -> Invoice:
        """Get invoice by ID or raise exception."""
        invoice = self.get_invoice(db, store_id, invoice_id)
        if not invoice:
            raise InvoiceNotFoundException(invoice_id)
        return invoice

    def get_invoice_by_number(
        self, db: Session, store_id: int, invoice_number: str
    ) -> Invoice | None:
        """Get invoice by invoice number."""
        return (
            db.query(Invoice)
            .filter(
                and_(
                    Invoice.invoice_number == invoice_number,
                    Invoice.store_id == store_id,
                )
            )
            .first()
        )

    def get_invoice_by_order_id(
        self, db: Session, store_id: int, order_id: int
    ) -> Invoice | None:
        """Get invoice by order ID."""
        return (
            db.query(Invoice)
            .filter(
                and_(
                    Invoice.order_id == order_id,
                    Invoice.store_id == store_id,
                )
            )
            .first()
        )

    def list_invoices(
        self,
        db: Session,
        store_id: int,
        status: str | None = None,
        page: int = 1,
        per_page: int = 20,
    ) -> tuple[list[Invoice], int]:
        """List invoices for store with pagination."""
        query = db.query(Invoice).filter(Invoice.store_id == store_id)

        if status:
            query = query.filter(Invoice.status == status)

        total = query.count()

        invoices = (
            query.order_by(Invoice.invoice_date.desc())
            .offset((page - 1) * per_page)
            .limit(per_page)
            .all()
        )

        return invoices, total

    # =========================================================================
    # Invoice Status Management
    # =========================================================================

    def update_status(
        self,
        db: Session,
        store_id: int,
        invoice_id: int,
        new_status: str,
    ) -> Invoice:
        """Update invoice status."""
        invoice = self.get_invoice_or_raise(db, store_id, invoice_id)

        valid_statuses = [s.value for s in InvoiceStatus]
        if new_status not in valid_statuses:
            raise InvalidInvoiceStatusTransitionException(
                current_status=invoice.status,
                new_status=new_status,
                reason=f"Invalid status: {new_status}",
            )

        if invoice.status == InvoiceStatus.CANCELLED.value:
            raise InvalidInvoiceStatusTransitionException(
                current_status=invoice.status,
                new_status=new_status,
                reason="Cannot change status of cancelled invoice",
            )

        invoice.status = new_status
        invoice.updated_at = datetime.now(UTC)
        db.flush()
        db.refresh(invoice)

        logger.info(f"Updated invoice {invoice.invoice_number} status to {new_status}")
        return invoice

    def mark_as_issued(
        self, db: Session, store_id: int, invoice_id: int
    ) -> Invoice:
        """Mark invoice as issued."""
        return self.update_status(db, store_id, invoice_id, InvoiceStatus.ISSUED.value)

    def mark_as_paid(
        self, db: Session, store_id: int, invoice_id: int
    ) -> Invoice:
        """Mark invoice as paid."""
        return self.update_status(db, store_id, invoice_id, InvoiceStatus.PAID.value)

    def cancel_invoice(
        self, db: Session, store_id: int, invoice_id: int
    ) -> Invoice:
        """Cancel invoice."""
        return self.update_status(db, store_id, invoice_id, InvoiceStatus.CANCELLED.value)

    # =========================================================================
    # Statistics
    # =========================================================================

    def get_invoice_stats(
        self, db: Session, store_id: int
    ) -> dict[str, Any]:
        """Get invoice statistics for store."""
        total_count = (
            db.query(func.count(Invoice.id))
            .filter(Invoice.store_id == store_id)
            .scalar()
            or 0
        )

        total_revenue = (
            db.query(func.sum(Invoice.total_cents))
            .filter(
                and_(
                    Invoice.store_id == store_id,
                    Invoice.status.in_([
                        InvoiceStatus.ISSUED.value,
                        InvoiceStatus.PAID.value,
                    ]),
                )
            )
            .scalar()
            or 0
        )

        draft_count = (
            db.query(func.count(Invoice.id))
            .filter(
                and_(
                    Invoice.store_id == store_id,
                    Invoice.status == InvoiceStatus.DRAFT.value,
                )
            )
            .scalar()
            or 0
        )

        paid_count = (
            db.query(func.count(Invoice.id))
            .filter(
                and_(
                    Invoice.store_id == store_id,
                    Invoice.status == InvoiceStatus.PAID.value,
                )
            )
            .scalar()
            or 0
        )

        return {
            "total_invoices": total_count,
            "total_revenue_cents": total_revenue,
            "total_revenue": total_revenue / 100 if total_revenue else 0,
            "draft_count": draft_count,
            "paid_count": paid_count,
        }

    # =========================================================================
    # PDF Generation
    # =========================================================================

    def generate_pdf(
        self,
        db: Session,
        store_id: int,
        invoice_id: int,
        force_regenerate: bool = False,
    ) -> str:
        """Generate PDF for an invoice."""
        from app.modules.orders.services.invoice_pdf_service import invoice_pdf_service

        invoice = self.get_invoice_or_raise(db, store_id, invoice_id)
        return invoice_pdf_service.generate_pdf(db, invoice, force_regenerate)

    def get_pdf_path(
        self,
        db: Session,
        store_id: int,
        invoice_id: int,
    ) -> str | None:
        """Get PDF path for an invoice if it exists."""
        from app.modules.orders.services.invoice_pdf_service import invoice_pdf_service

        invoice = self.get_invoice_or_raise(db, store_id, invoice_id)
        return invoice_pdf_service.get_pdf_path(invoice)


# Singleton instance
invoice_service = InvoiceService()
