"""
Billing models: Invoice, InvoiceItem, Receipt.
Manages complete invoice lifecycle with purchased services and receipt generation.
"""
import enum
from datetime import datetime, date
from sqlalchemy import Column, String, Integer, Float, Enum, ForeignKey, DateTime, Date, Text, Boolean, JSON
from sqlalchemy.orm import relationship

from app.models.base import BaseModel


class InvoiceStatus(str, enum.Enum):
    """Invoice lifecycle status."""
    DRAFT = "draft"
    ISSUED = "issued"
    PAID = "paid"
    PARTIALLY_PAID = "partially_paid"
    OVERDUE = "overdue"
    CANCELLED = "cancelled"
    REFUNDED = "refunded"


class InvoiceType(str, enum.Enum):
    """Invoice type."""
    ACCOMMODATION = "accommodation"    # Chambre + nuitées
    SERVICES = "services"              # Services seuls (spa, resto, etc.)
    MIXED = "mixed"                    # Hébergement + services
    PROFORMA = "proforma"              # Facture pro forma (avant séjour)
    CREDIT_NOTE = "credit_note"        # Avoir / note de crédit


class ServiceCategory(str, enum.Enum):
    """Category of purchased service."""
    ACCOMMODATION = "accommodation"
    RESTAURANT = "restaurant"
    BAR = "bar"
    ROOM_SERVICE = "room_service"
    SPA = "spa"
    LAUNDRY = "laundry"
    MINIBAR = "minibar"
    PARKING = "parking"
    TELEPHONE = "telephone"
    TRANSPORT = "transport"
    EXCURSION = "excursion"
    BOUTIQUE = "boutique"
    MISC = "misc"


class ReceiptStatus(str, enum.Enum):
    """Receipt status."""
    ISSUED = "issued"
    CANCELLED = "cancelled"


class Invoice(BaseModel):
    """
    Complete invoice for a client stay or service purchase.

    An invoice is generated from a folio or standalone for services.
    It tracks all line items (services purchased), payments, and generates receipts.
    """
    __tablename__ = "invoices"

    # Reference
    invoice_number = Column(String(50), unique=True, nullable=False, index=True)
    invoice_type = Column(Enum(InvoiceType), default=InvoiceType.MIXED, nullable=False)
    status = Column(Enum(InvoiceStatus), default=InvoiceStatus.DRAFT, nullable=False, index=True)

    # Relations
    establishment_id = Column(Integer, ForeignKey("establishments.id", ondelete="CASCADE"), nullable=False, index=True)
    client_id = Column(Integer, ForeignKey("clients.id", ondelete="SET NULL"), nullable=True, index=True)
    reservation_id = Column(Integer, ForeignKey("reservations.id", ondelete="SET NULL"), nullable=True, index=True)
    folio_id = Column(Integer, ForeignKey("folios.id", ondelete="SET NULL"), nullable=True, index=True)
    created_by_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)

    # Dates
    issue_date = Column(Date, nullable=False, default=date.today)
    due_date = Column(Date, nullable=True)

    # Financial totals
    subtotal_ht = Column(Float, default=0.0, nullable=False)
    total_tax = Column(Float, default=0.0, nullable=False)
    total_ttc = Column(Float, default=0.0, nullable=False)
    total_paid = Column(Float, default=0.0, nullable=False)
    balance_due = Column(Float, default=0.0, nullable=False)
    currency = Column(String(3), default="XOF", nullable=False)

    # Discount
    discount_percent = Column(Float, default=0.0, nullable=False)
    discount_amount = Column(Float, default=0.0, nullable=False)

    # Client info snapshot (for historical accuracy)
    client_name = Column(String(200), nullable=True)
    client_email = Column(String(200), nullable=True)
    client_address = Column(Text, nullable=True)
    client_tax_id = Column(String(100), nullable=True)

    # Notes & terms
    notes = Column(Text, nullable=True)
    payment_terms = Column(Text, nullable=True)

    # Credit note reference
    original_invoice_id = Column(Integer, ForeignKey("invoices.id", ondelete="SET NULL"), nullable=True)

    # Relationships
    establishment = relationship("Establishment")
    client = relationship("Client")
    reservation = relationship("Reservation")
    folio = relationship("Folio")
    created_by = relationship("User", foreign_keys=[created_by_id])
    items = relationship("InvoiceItem", back_populates="invoice", cascade="all, delete-orphan",
                         order_by="InvoiceItem.created_at")
    receipts = relationship("Receipt", back_populates="invoice", cascade="all, delete-orphan")

    def recalculate_totals(self) -> None:
        """Recalculate invoice totals from items."""
        active_items = [i for i in self.items if not i.is_cancelled]
        self.subtotal_ht = round(sum(i.total_ht for i in active_items), 2)
        self.total_tax = round(sum(i.total_tax for i in active_items), 2)
        gross_ttc = round(self.subtotal_ht + self.total_tax, 2)

        # Apply discount
        if self.discount_percent and self.discount_percent > 0:
            self.discount_amount = round(gross_ttc * self.discount_percent / 100, 2)
        self.total_ttc = round(gross_ttc - self.discount_amount, 2)

        # Balance
        total_valid_payments = sum(
            r.amount for r in self.receipts
            if r.status == ReceiptStatus.ISSUED
        )
        self.total_paid = round(total_valid_payments, 2)
        self.balance_due = round(self.total_ttc - self.total_paid, 2)

        # Update status
        if self.balance_due <= 0 and self.total_ttc > 0:
            self.status = InvoiceStatus.PAID
        elif self.total_paid > 0:
            self.status = InvoiceStatus.PARTIALLY_PAID

    def __repr__(self) -> str:
        return f"<Invoice {self.invoice_number} ({self.status.value})>"


class InvoiceItem(BaseModel):
    """
    Line item on an invoice representing a purchased service or product.
    """
    __tablename__ = "invoice_items"

    invoice_id = Column(Integer, ForeignKey("invoices.id", ondelete="CASCADE"), nullable=False, index=True)

    # Service info
    service_category = Column(Enum(ServiceCategory), nullable=False)
    service_code = Column(String(50), nullable=True)  # e.g., "HEBERG", "REST"
    description = Column(String(500), nullable=False)
    service_date = Column(Date, nullable=True)  # Date the service was rendered

    # Pricing
    quantity = Column(Float, default=1.0, nullable=False)
    unit = Column(String(30), default="unité", nullable=False)  # nuit, repas, heure, etc.
    unit_price_ht = Column(Float, nullable=False)
    tax_rate = Column(Float, default=0.0, nullable=False)
    total_ht = Column(Float, nullable=False)
    total_tax = Column(Float, nullable=False)
    total_ttc = Column(Float, nullable=False)

    # Optional discount per line
    discount_percent = Column(Float, default=0.0, nullable=False)
    discount_amount = Column(Float, default=0.0, nullable=False)

    # Cancellation
    is_cancelled = Column(Boolean, default=False, nullable=False)
    cancelled_reason = Column(String(500), nullable=True)

    # Extra data (room number, date, etc.)
    extra_data = Column(JSON, nullable=True)

    # Relationships
    invoice = relationship("Invoice", back_populates="items")

    def calculate_totals(self) -> None:
        """Calculate item totals."""
        base = round(self.quantity * self.unit_price_ht, 2)
        if self.discount_percent and self.discount_percent > 0:
            self.discount_amount = round(base * self.discount_percent / 100, 2)
        self.total_ht = round(base - self.discount_amount, 2)
        self.total_tax = round(self.total_ht * (self.tax_rate / 100), 2)
        self.total_ttc = round(self.total_ht + self.total_tax, 2)

    def __repr__(self) -> str:
        return f"<InvoiceItem {self.description}: {self.total_ttc}>"


class Receipt(BaseModel):
    """
    Payment receipt for an invoice.
    Generated when a payment is recorded against an invoice.
    """
    __tablename__ = "receipts"

    receipt_number = Column(String(50), unique=True, nullable=False, index=True)
    invoice_id = Column(Integer, ForeignKey("invoices.id", ondelete="CASCADE"), nullable=False, index=True)
    received_by_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)

    # Payment details
    payment_method = Column(String(50), nullable=False)  # cash, card, mobile, etc.
    payment_reference = Column(String(200), nullable=True)  # Transaction ID, check #, etc.
    amount = Column(Float, nullable=False)
    currency = Column(String(3), default="XOF", nullable=False)
    payment_date = Column(DateTime, default=datetime.utcnow, nullable=False)

    # Status
    status = Column(Enum(ReceiptStatus), default=ReceiptStatus.ISSUED, nullable=False)
    cancellation_reason = Column(Text, nullable=True)

    # Notes
    notes = Column(Text, nullable=True)

    # Relationships
    invoice = relationship("Invoice", back_populates="receipts")
    received_by = relationship("User", foreign_keys=[received_by_id])

    def __repr__(self) -> str:
        return f"<Receipt {self.receipt_number}: {self.amount} {self.currency}>"
