from django.db import models
from django.db.models import Sum
from django.utils import timezone
from accounts.models import User
from inventory.models import Product
from finance.models import Account


class Customer(models.Model):
    name = models.CharField(max_length=255)
    phone = models.CharField(max_length=20, blank=True)
    email = models.EmailField(blank=True)
    address = models.TextField(blank=True)
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("name",)

    def __str__(self):
        return self.name

    @property
    def balance_due(self):
        totals = self.ledger_entries.aggregate(
            total_debit=Sum("debit"),
            total_credit=Sum("credit"),
        )
        total_debit = totals["total_debit"]
        total_credit = totals["total_credit"]

        if total_debit is None and total_credit is None:
            # Fallback for historical data before ledger entries existed.
            return sum(invoice.balance_due for invoice in self.invoices.all())

        balance = (total_debit or 0) - (total_credit or 0)
        return balance if balance > 0 else 0


class Sale(models.Model):
    class PaymentMethod(models.TextChoices):
        CASH = 'cash', 'Cash'
        MOBILE = 'mobile', 'Mobile Money'
        BANK = 'bank', 'Bank Transfer'
        CREDIT = 'credit', 'Credit'

    class Status(models.TextChoices):
        COMPLETED = 'completed', 'Completed'
        CANCELLED = 'cancelled', 'Cancelled'

    reference = models.CharField(max_length=50, unique=True)   # e.g. SALE-0001
    customer = models.ForeignKey(
        Customer, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='sales'
    )
    customer_name = models.CharField(max_length=255, blank=True)
    account = models.ForeignKey(
        Account, on_delete=models.PROTECT,
        null=True, blank=True, related_name='sales'
    )
    payment_method = models.CharField(
        max_length=20,
        choices=PaymentMethod.choices,
        default=PaymentMethod.CASH
    )
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.COMPLETED
    )
    discount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    note = models.TextField(blank=True)
    date = models.DateField(default=timezone.now)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.reference

    # ── Calculated totals ──────────────────────────────────────────
    @property
    def subtotal(self):
        return sum(item.line_total for item in self.items.all())

    @property
    def total(self):
        return self.subtotal - self.discount

    @property
    def total_cost(self):
        return sum(item.line_cost for item in self.items.all())

    @property
    def gross_profit(self):
        return self.total - self.total_cost

    @property
    def customer_display(self):
        return self.customer.name if self.customer else (self.customer_name or 'Walk-in')


class SaleItem(models.Model):
    sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=15, decimal_places=2)
    unit_price = models.DecimalField(max_digits=15, decimal_places=2)  # selling price at time of sale
    unit_cost = models.DecimalField(max_digits=15, decimal_places=2)   # cost price at time of sale

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"

    @property
    def line_total(self):
        return self.quantity * self.unit_price

    @property
    def line_cost(self):
        return self.quantity * self.unit_cost

    @property
    def line_profit(self):
        return self.line_total - self.line_cost

class Invoice(models.Model):
    class Status(models.TextChoices):
        UNPAID = 'unpaid', 'Unpaid'
        PARTIAL = 'partial', 'Partially Paid'
        PAID = 'paid', 'Paid'
        CANCELLED = 'cancelled', 'Cancelled'

    reference = models.CharField(max_length=50, unique=True)   # INV-0001
    customer = models.ForeignKey(
        Customer, on_delete=models.SET_NULL,
        null=True, blank=True, related_name='invoices'
    )
    customer_name = models.CharField(max_length=255)
    customer_phone = models.CharField(max_length=20, blank=True)
    customer_email = models.EmailField(blank=True)
    customer_address = models.TextField(blank=True)
    status = models.CharField(
        max_length=20,
        choices=Status.choices,
        default=Status.UNPAID
    )
    discount = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    note = models.TextField(blank=True)
    issue_date = models.DateField(default=timezone.now)
    due_date = models.DateField(null=True, blank=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.reference

    @property
    def subtotal(self):
        return sum(item.line_total for item in self.items.all())

    @property
    def total(self):
        return self.subtotal - self.discount

    @property
    def total_paid(self):
        return sum(
            p.amount for p in self.payments.filter(is_cancelled=False)
        )

    @property
    def balance_due(self):
        return self.total - self.total_paid

    @property
    def is_overdue(self):
        if self.due_date and self.status != self.Status.PAID:
            from django.utils import timezone
            return self.due_date < timezone.now().date()
        return False

    def update_status(self):
        """Auto-update status based on payments."""
        if self.total_paid <= 0:
            self.status = self.Status.UNPAID
        elif self.total_paid >= self.total:
            self.status = self.Status.PAID
        else:
            self.status = self.Status.PARTIAL
        self.save()

    @property
    def customer_display(self):
        return self.customer.name if self.customer else self.customer_name


class InvoiceItem(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='items')
    product = models.ForeignKey(
        Product, on_delete=models.PROTECT,
        null=True, blank=True
    )
    description = models.CharField(max_length=255)   # manual description if no product
    quantity = models.DecimalField(max_digits=15, decimal_places=2, default=1)
    unit_price = models.DecimalField(max_digits=15, decimal_places=2)
    unit_cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)

    def __str__(self):
        return f"{self.description} x {self.quantity}"

    @property
    def line_total(self):
        return self.quantity * self.unit_price


class InvoicePayment(models.Model):
    invoice = models.ForeignKey(Invoice, on_delete=models.PROTECT, related_name='payments')
    account = models.ForeignKey(
        Account, on_delete=models.PROTECT, related_name='invoice_payments'
    )
    amount = models.DecimalField(max_digits=15, decimal_places=2)
    payment_method = models.CharField(
        max_length=20,
        choices=Sale.PaymentMethod.choices,
        default=Sale.PaymentMethod.CASH
    )
    note = models.CharField(max_length=255, blank=True)
    date = models.DateField(default=timezone.now)
    is_cancelled = models.BooleanField(default=False)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True
    )
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return f"{self.invoice.reference} | {self.amount}"


class PaymentAllocation(models.Model):
    """
    Tracks how an invoice payment is allocated to an invoice line of receivables.
    In current flow each payment targets one invoice, but the model keeps
    accounting trail explicit for future multi-invoice allocations.
    """

    payment = models.OneToOneField(
        InvoicePayment, on_delete=models.CASCADE, related_name="allocation"
    )
    invoice = models.ForeignKey(
        Invoice, on_delete=models.PROTECT, related_name="allocations"
    )
    amount = models.DecimalField(max_digits=15, decimal_places=2)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="payment_allocations_created",
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("-created_at",)

    def __str__(self):
        return f"{self.payment_id} -> {self.invoice.reference} ({self.amount})"

    def clean(self):
        if self.payment and self.invoice_id and self.payment.invoice_id != self.invoice_id:
            from django.core.exceptions import ValidationError

            raise ValidationError("Allocation invoice must match payment invoice.")

    def save(self, *args, **kwargs):
        self.full_clean()
        super().save(*args, **kwargs)


class CustomerLedgerEntry(models.Model):
    class EntryType(models.TextChoices):
        SALE = "sale", "Sale"
        INVOICE = "invoice", "Invoice"
        PAYMENT = "payment", "Payment"
        RETURN = "return", "Return"
        ADJUSTMENT = "adjustment", "Adjustment"

    customer = models.ForeignKey(
        Customer, on_delete=models.CASCADE, related_name="ledger_entries"
    )
    entry_type = models.CharField(max_length=20, choices=EntryType.choices)
    reference = models.CharField(max_length=100, blank=True)
    note = models.CharField(max_length=255, blank=True)
    debit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    credit = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    date = models.DateField(default=timezone.now)
    sale = models.ForeignKey(
        Sale, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="ledger_entries",
    )
    invoice = models.ForeignKey(
        Invoice, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="ledger_entries",
    )
    payment = models.ForeignKey(
        InvoicePayment, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="ledger_entries",
    )
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name="customer_ledger_entries",
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ("-date", "-created_at")
        indexes = [
            models.Index(fields=["customer", "date"]),
            models.Index(fields=["reference"]),
        ]

    def __str__(self):
        return (
            f"{self.customer.name} | {self.get_entry_type_display()} | "
            f"D:{self.debit} C:{self.credit}"
        )

    @property
    def net_amount(self):
        return self.debit - self.credit


class SalesReturn(models.Model):
    class Status(models.TextChoices):
        DRAFT = 'draft', 'Draft'
        PROCESSED = 'processed', 'Processed'
        CANCELLED = 'cancelled', 'Cancelled'

    reference = models.CharField(max_length=50, unique=True)
    sale = models.ForeignKey(
        Sale, on_delete=models.PROTECT, related_name='returns'
    )
    reason = models.TextField(blank=True)
    status = models.CharField(
        max_length=20, choices=Status.choices, default=Status.DRAFT
    )
    date = models.DateField(default=timezone.now)
    processed_date = models.DateField(null=True, blank=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='sales_returns_created'
    )
    processed_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, null=True, blank=True,
        related_name='sales_returns_processed'
    )
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ('-date', '-created_at')

    def __str__(self):
        return self.reference

    @property
    def total(self):
        return sum(item.line_total for item in self.items.all())


class SalesReturnItem(models.Model):
    sales_return = models.ForeignKey(
        SalesReturn, on_delete=models.CASCADE, related_name='items'
    )
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=15, decimal_places=2)
    unit_price = models.DecimalField(max_digits=15, decimal_places=2, default=0)
    unit_cost = models.DecimalField(max_digits=15, decimal_places=2, default=0)

    def __str__(self):
        return f"{self.product.name} x {self.quantity}"

    @property
    def line_total(self):
        return self.quantity * self.unit_price
