from decimal import Decimal

from django.db import models
from django.db.models import Q, Sum
from shop_management.models.base import DataRoot
from shop_management.models.data.shop import Shop
from shop_management.models.data.product import Product
from shop_management.models.data.utils import Currency, Unit, PaymentStatus
from shop_management.models.data.customer import Customer  # adjust the import path if needed
from django.utils import timezone
from shop_management.models.data.organization import Warehouse

class Sale(DataRoot):
    class DocumentStatus(models.TextChoices):
        DRAFT = "DRAFT", "Draft"
        POSTED = "POSTED", "Posted"
        VOID = "VOID", "Cancelled"

    class SalesType(models.TextChoices):
        CASH = "CASH", "Cash"
        CREDIT = "CREDIT", "Credit"

    class DiscountType(models.TextChoices):
        FIXED = "FIXED", "Fixed"
        PERCENTAGE = "PERCENTAGE", "Percentage"

    shop = models.ForeignKey(Shop, on_delete=models.CASCADE, related_name='sales')
    customer = models.ForeignKey(
        Customer, 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='sales'
    )
    sale_date = models.DateField(default=timezone.now)
    amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, default=0)
    status = models.IntegerField(choices=PaymentStatus.choices, default=PaymentStatus.PENDING)
    currency = models.ForeignKey(
        Currency, 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='sale_items'
    )
    received_amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, default=0)
    notes = models.TextField(blank=True, null=True)
    general_customer_name = models.CharField(max_length=255, blank=True, null=True)
    general_customer_phone = models.CharField(max_length=50, blank=True, null=True)
    sale_number = models.CharField(max_length=50, null=True, blank=True)
    warehouse = models.ForeignKey(Warehouse, null=True, blank=True, on_delete=models.PROTECT, related_name="sales")
    invoice_date = models.DateField(null=True, blank=True)
    due_date = models.DateField(null=True, blank=True)
    sales_type = models.CharField(max_length=10, choices=SalesType.choices, default=SalesType.CASH)
    exchange_rate_used = models.DecimalField(max_digits=20, decimal_places=6, default=1)
    discount_type = models.CharField(max_length=12, choices=DiscountType.choices, default=DiscountType.FIXED)
    discount_value = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    discount_amount = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    calculated_subtotal = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    calculated_total = models.DecimalField(max_digits=18, decimal_places=2, default=0)
    document_status = models.CharField(max_length=10, choices=DocumentStatus.choices, default=DocumentStatus.DRAFT)
    uses_inventory_engine = models.BooleanField(default=False)
    posted_at = models.DateTimeField(null=True, blank=True)
    posted_by = models.ForeignKey("account.User", null=True, blank=True, on_delete=models.PROTECT, related_name="sales_posted")
    voided_at = models.DateTimeField(null=True, blank=True)
    voided_by = models.ForeignKey("account.User", null=True, blank=True, on_delete=models.PROTECT, related_name="sales_voided")
    void_reason = models.TextField(blank=True)
    replaces_sale = models.OneToOneField("self", null=True, blank=True, on_delete=models.PROTECT, related_name="replacement_sale")
    # Customer's outstanding balance just before this sale was posted.
    # previous_balance: total converted into the sale currency.
    # previous_balance_details: one row per currency:
    #   {"currency_id", "currency", "amount", "rate", "converted"}
    # where rate converts 1 unit of that currency into the sale currency
    # (rate/converted are null when no exchange rate is configured).
    previous_balance = models.DecimalField(max_digits=18, decimal_places=2, null=True, blank=True)
    previous_balance_details = models.JSONField(null=True, blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["shop", "sale_number"], name="unique_sale_number_per_shop"
            )
        ]

    def __str__(self):
        customer_name = self.customer.name if self.customer else "Walk-in Customer"
        return f"Sale for {self.shop.name} to {customer_name}"

    @property
    def total_amount(self):
        """
        Calculate the total sale amount using sale items.
        Each sale item's total is computed as (quantity * sale_price).
        Returns a dictionary with the sale's currency title as the key.
        """
        totals = {}
        key = self.currency.title if self.currency else "Unknown Currency"
        totals[key] = sum(item.quantity * item.sale_price for item in self.sale_items.all())
        return totals

    @property
    def effective_total_amount(self):
        base_totals = self.total_amount
        amount = self.amount or 0
        effective = {}
        for currency, total in base_totals.items():
            effective[currency] = total - amount
        return effective

    @property
    def total_paid(self):
        """
        Returns the total paid amount (received_amount + SalePayment entries)
        keyed by the sale's currency.
        """
        key = self.currency.title if self.currency else "Unknown Currency"
        received = self.received_amount or Decimal("0")

        # Count payments recorded later via SalePayment. If a payment has no currency,
        # treat it as the sale currency; otherwise only sum matching-currency payments.
        payments_qs = self.payments.all()
        if self.currency_id:
            payments_qs = payments_qs.filter(Q(currency_id=self.currency_id) | Q(currency__isnull=True))
        else:
            payments_qs = payments_qs.filter(currency__isnull=True)

        payment_sum = payments_qs.aggregate(total=Sum("amount")).get("total") or Decimal("0")
        allocated = self.customer_payment_allocations.filter(
            payment__status="POSTED"
        ).aggregate(total=Sum("allocated_amount")).get("total") or Decimal("0")
        credit_applied = Decimal("0")
        if self.document_status == self.DocumentStatus.POSTED:
            credit_applied = self.credit_applications.aggregate(
                total=Sum("sale_amount")
            ).get("total") or Decimal("0")
        return {key: received + payment_sum + allocated + credit_applied}

    @property
    def total_remained_amount(self):
        effective_total = self.effective_total_amount
        total_paid = self.total_paid
        remained = {}
        for currency, amount in effective_total.items():
            return_credit = self.sale_returns.filter(
                Q(uses_inventory_engine=False) | Q(document_status="POSTED")
            ).aggregate(total=Sum("total_refound_amount"))["total"] or Decimal("0")
            remained[currency] = amount - total_paid.get(currency, 0) - return_credit
        return remained

    def update_status(self):
        effective_total = self.effective_total_amount
        total_paid = self.total_paid

        fully_paid = True
        for currency, amount in effective_total.items():
            if total_paid.get(currency, 0) < amount:
                fully_paid = False
                break

        new_status = PaymentStatus.COMPLETED if fully_paid else PaymentStatus.PENDING
        if self.status != new_status:
            self.status = new_status
            self.save(update_fields=['status'])
    
    def get_discount_percentage(self):
        """Calculate amount as percentage of subtotal"""
        subtotal = sum(item.quantity * item.sale_price for item in self.sale_items.all())
        if subtotal > 0 and self.amount:
            return (self.amount / subtotal) * 100
        return 0
    
    def set_discount_percentage(self, percentage):
        """Set amount based on percentage of subtotal"""
        subtotal = sum(item.quantity * item.sale_price for item in self.sale_items.all())
        if subtotal > 0:
            self.amount = (percentage / 100) * subtotal
            self.save(update_fields=['amount'])



from shop_management.models.data.purchase import PurchaseItem


class SaleItem(DataRoot):
    sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name='sale_items')
    warehouse = models.ForeignKey(
        Warehouse,
        null=True,
        blank=True,
        on_delete=models.PROTECT,
        related_name="sale_items",
        help_text="Warehouse from which this line item is issued. Legacy rows fall back to the sale warehouse.",
    )
    product = models.ForeignKey(
        Product, 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='sale_items'
    )
    purchase_item = models.ForeignKey(
        PurchaseItem,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='sale_items',
        help_text="The specific purchase item this sale item was taken from."
    )
    quantity = models.PositiveIntegerField()
    unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
    buy_price = models.DecimalField(max_digits=14, decimal_places=4, null=True, blank=True)
    sale_price = models.DecimalField(max_digits=14, decimal_places=4)
    cogs_unit_cost = models.DecimalField(max_digits=18, decimal_places=6, null=True, blank=True)
    cogs_total = models.DecimalField(max_digits=18, decimal_places=2, default=0)

    def __str__(self):
        product_name = self.product.name if self.product else "Unknown Product"
        return f"{self.quantity} x {product_name}"


class SaleItemBatchAllocation(DataRoot):
    sale_item = models.ForeignKey(SaleItem, related_name="batch_allocations", on_delete=models.PROTECT)
    batch = models.ForeignKey("shop_management.InventoryBatch", related_name="sale_allocations", on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=18, decimal_places=4)
    unit_cost = models.DecimalField(max_digits=18, decimal_places=6, default=0)

    class Meta:
        constraints = [
            models.UniqueConstraint(fields=["sale_item", "batch"], name="unique_sale_item_batch")
        ]


