from collections import defaultdict
from django.contrib import admin
from django.conf import settings
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.db.models import F, ExpressionWrapper, DecimalField, Sum
from uuid import uuid4
from humanresource.models import Currency, Employment
from inventory.validators import validate_file_size
from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from django.db import transaction
from django.utils import timezone
from django.utils.functional import cached_property
from jalali_date import date2jalali, datetime2jalali
from decimal import Decimal
from django.core.exceptions import ValidationError
from datetime import date
from django.db.models.signals import post_save
from django.dispatch import receiver

class Collection(models.Model):
    title = models.CharField(max_length=191)
    featured_product = models.ForeignKey(
        'Product', on_delete=models.SET_NULL, null=True, related_name='+', blank=True)

    class Meta:
        ordering = ['title']
        verbose_name = 'Collection'
        verbose_name_plural = 'Collections'

    def __str__(self) -> str:
        return self.title


class PackingStyle(models.Model):
    title = models.CharField(max_length=191)

    def __str__(self):
        return self.title


class MeasurementUnit(models.Model):
    title = models.CharField(max_length=191)

    def __str__(self):
        return self.title


class Product(models.Model):
    title = models.CharField(max_length=191)
    slug = models.SlugField()
    abbreviation = models.CharField(max_length=10)
    description = models.TextField(null=True, blank=True)
    unit_price = models.DecimalField(
        max_digits=6,
        decimal_places=2,
        validators=[MinValueValidator(1)])
    commission = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    currency = models.ForeignKey(
        Currency, on_delete=models.PROTECT)
    inventory = models.IntegerField(
        validators=[MinValueValidator(0)])
    last_update = models.DateTimeField(
        auto_now=True)
    collection = models.ForeignKey(
        Collection, on_delete=models.PROTECT, related_name='products')
    packing_style = models.ForeignKey(
        PackingStyle, on_delete=models.PROTECT)
    measurement_unit = models.ForeignKey(
        MeasurementUnit, on_delete=models.PROTECT)
    discount_limit = models.DecimalField(
        max_digits=2,
        decimal_places=0,
        validators=[
            MinValueValidator(0),
            MaxValueValidator(100)
        ],
        default=0
    )
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='product_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='product_updated',
        null=True,
        blank=True
    )

    def __str__(self) -> str:
        return f'{self.title}'

    class Meta:
        ordering = ['title']
        verbose_name = 'Product'
        verbose_name_plural = 'Products'
        permissions = [
            ('client_cash_report','can see clients cash report?'),
        ]

class Supplier(models.Model):
    name = models.CharField(max_length=191)
    country = models.CharField(max_length=191)
    owner = models.CharField(max_length=191)
    contacted_person = models.CharField(max_length=191)
    email_address = models.EmailField()
    phone_number = models.CharField(max_length=191)
    bank_account_number = models.CharField(max_length=191)
    remarks = models.TextField(blank=True, null=True, default='N/A')

    def __str__(self):
        return self.name


class ProductBatches(models.Model):
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    batch_number = models.CharField(max_length=191)
    expiry_date = models.DateField(default='2026-01-01')
    quantity = models.PositiveIntegerField(default=0)
    purchased_price = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    remarks = models.CharField(max_length=191, default='no remarks')

    def __str__(self):
        return f'{self.product} - Batch: {self.batch_number} - QYT:{self.quantity} - Price:{self.product.unit_price}'


class ProductImage(models.Model):
    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField(
        upload_to='inventory/product/images',
        validators=[validate_file_size])

    class Meta:
        verbose_name = 'Products image'
        verbose_name_plural = 'Products images'


class Consumer(models.Model):
    name = models.CharField(max_length=191)
    business_nature = models.CharField(
        max_length=191, null=True, blank=True)
    in_charge = models.CharField(
        max_length=191, blank=True, null=True)
    phone_number = models.CharField(max_length=191)
    email = models.EmailField(blank=True, null=True)
    website = models.CharField(
        max_length=191, null=True, blank=True)
    account_number = models.CharField(
        max_length=191, null=True, blank=True)
    address = models.CharField(
        max_length=191, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_updated',
        null=True,
        blank=True
    )

    class Meta:
        verbose_name = 'Consumer'
        verbose_name_plural = 'Consumers'

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        pass
        super(Consumer, self).save(*args, **kwargs)

        if not ConsumerAccount.objects.filter(consumer=self).exists():
            account = ConsumerAccount.objects.create(
                consumer=self,
                tx_number=f'TXC({self.id})',
                details="Auto Generated Account!"
            )


class ConsumerProductDiscount(models.Model):
    consumer = models.ForeignKey(Consumer, on_delete=models.PROTECT)
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    discount = models.PositiveIntegerField(
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        default=0
    )
    unit_price = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        null=True,
        blank=True
    )

    class Meta:
        unique_together = ('consumer', 'product')
        verbose_name = 'Consumer Product Discount'
        verbose_name_plural = 'Consumer Product Discounts'

    def __str__(self):
        price_display = f" @ {self.unit_price}" if self.unit_price is not None else ""
        return f"{self.consumer} - {self.product}: {self.discount}%{price_display}"


class ConsumerAccount(models.Model):
    consumer = models.ForeignKey(
        Consumer, on_delete=models.CASCADE)
    tx_date = models.DateField(auto_now=True)
    tx_number = models.CharField(
        max_length=191, unique=True)
    details = models.CharField(
        max_length=191, default='Details')
    previous_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    credit = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    debit = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    current_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_accounts_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_accounts_updated',
        null=True,
        blank=True
    )

    class Meta:
        verbose_name = 'Consumer Account'
        verbose_name_plural = 'Consumer Accounts'
        permissions = [
            ("can_print_slip", "Can print consumer account slip?"),
        ]

    def save(self, *args, **kwargs):
        # calculate the current balance by adding the previous balance, total credit, and subtracting total debit
        self.current_balance = self.previous_balance + \
            self.credit - self.debit
        super(ConsumerAccount, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.consumer} - {self.details}'

    @cached_property
    def consumer_payment(self):
        """Return linked consumer payment if this entry was generated from it."""
        if self.tx_number.startswith('CB00-'):
            try:
                payment_id = int(self.tx_number.split('CB00-')[1])
            except (ValueError, IndexError):
                return None
            return (
                Payment_From_Consumer.objects
                .filter(id=payment_id)
                .only('created_at', 'remarks')
                .first()
            )
        return None

    @property
    def display_tx_date(self):
        """Prefer the payment date for debit entries created by consumer payments."""
        payment = self.consumer_payment
        if payment and payment.created_at:
            return payment.created_at.date()
        return self.tx_date

    @property
    def payment_remarks(self):
        payment = self.consumer_payment
        if payment and payment.remarks:
            return payment.remarks
        return ""

    @property
    def receipt_no(self):
        """Return receipt number if this entry is linked to a payment."""
        if self.tx_number.startswith('CB00-'):
            try:
                payment_id = int(self.tx_number.split('CB00-')[1])
                payment = Payment_From_Consumer.objects.filter(id=payment_id).first()
                if payment:
                    return payment.receipt_no
            except (ValueError, IndexError):
                pass
        return None
class Payment_From_Consumer(models.Model):
    CASH_IN_HAND = 'C/H'
    CASH_IN_BANK = 'C/B'
    PAYBACK_COMMISSION = 'P/C'
    CASH_ACCOUNTS_CHOICES = [
        (CASH_IN_HAND, 'CASH IN HAND'),
        (CASH_IN_BANK, 'CASH IN BANK'),
        (PAYBACK_COMMISSION, 'PAYBACK AS COMMISSION')
    ]
    consumer = models.ForeignKey(
        Consumer, on_delete=models.PROTECT)
    received_by = models.ForeignKey(
        Employment, on_delete=models.PROTECT)
    received_in = models.CharField(
        max_length=3, choices=CASH_ACCOUNTS_CHOICES, default=CASH_IN_HAND)
    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    remain_amount = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    receipt_no = models.CharField(max_length=191, blank=True, null=True)
    remarks = models.TextField(blank=True, null=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='consumer_payment_created', null=True)
    updated_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='consumer_payment_updated', null=True)
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.consumer} - Bill: {self.id}'

    class Meta:
        verbose_name = 'Consumer Payment'
        verbose_name_plural = 'Consumers Payment'

    def save(self, *args, **kwargs):
        with transaction.atomic():
            from finance.models import CashAccount
            # Calculate the debit and credit amounts
            debit = self.amount_paid
            credit = 0

            # Retrieve the previous balance
            previous_balance = ConsumerAccount.objects.filter(
                consumer=self.consumer
            ).order_by('-id').values('current_balance').first()

            previous_balance = previous_balance['current_balance'] if previous_balance else 0

            # Calculate the current balance
            current_balance = previous_balance + credit - debit

            # Calculate the remain_amount
            self.remain_amount = current_balance

            # Save the Payment_To_Supplier instance
            super().save(*args, **kwargs)
# --------------------------------------------------------------------------------
#                       Cash Account Related codes
            # Calculate the debit and credit amounts
            amount_in = self.amount_paid
            amount_out = 0
            currency_instance = Currency.objects.first()
            current_date = date.today()

            # Retrieve the previous balance
            ca_previous_balance = CashAccount.objects.filter(
                account_type=self.received_in
            ).order_by('-id').values('current_balance').first()

            ca_previous_balance = ca_previous_balance['current_balance'] if ca_previous_balance else 0

            # Calculate the current balance
            ca_current_balance = ca_previous_balance + amount_in - amount_out

            # Check if an instance with the same tx_number exists
            existing_cash_account = CashAccount.objects.filter(
                tx_number=f'CP00-{self.id}'
            ).first()

            # Use transaction to handle the update or create logic
            with transaction.atomic():
                if existing_cash_account:
                    # Update the existing instance
                    existing_cash_account.date = current_date
                    existing_cash_account.amount_in = self.amount_paid
                    existing_cash_account.previous_balance = ca_previous_balance
                    existing_cash_account.current_balance = ca_current_balance
                    existing_cash_account.save()
                else:
                    # Create a new instance of CashAccount
                    cash_account = CashAccount(
                        date=current_date,
                        account_type=self.received_in,
                        tx_number=f'CP00-{self.id}',
                        description=f'Consumer Payment #{self.id}',
                        amount_in=self.amount_paid,
                        amount_out=0,
                        previous_balance=ca_previous_balance,
                        current_balance=ca_current_balance,
                        currency=currency_instance,
                        remarks=f'Cash account entry for consumer {self.consumer} payment!'
                    )
                    cash_account.save()


# -----------------------------------------------------------------------------------
            # Create or update the SupplierAccount entry
            details = f'Debited Receipt #{self.id}'
            tx_number = f'CB00-{self.id}'

            ConsumerAccount.objects.update_or_create(
                consumer=self.consumer,
                tx_number=tx_number,
                defaults={
                    'details': details,
                    'previous_balance': previous_balance,
                    'debit': debit,
                    'credit': credit,
                    'current_balance': current_balance,
                }
            )

            # Update the Payment_To_Supplier instance with the corrected remain_amount
            self.remain_amount = current_balance
            super().save(*args, **kwargs)


class SupplierAccount(models.Model):
    supplier = models.ForeignKey(
        Supplier, on_delete=models.CASCADE)
    tx_date = models.DateField(auto_now=True)
    tx_number = models.CharField(
        max_length=191, unique=True)
    details = models.CharField(
        max_length=191, default='Details')
    previous_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    credit = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    debit = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    current_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='supplier_accounts_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='supplier_accounts_updated',
        null=True,
        blank=True
    )

    class Meta:
        verbose_name = 'Supplier Account'
        verbose_name_plural = 'Supplier Accounts'

    def save(self, *args, **kwargs):
        self.current_balance = self.previous_balance + \
            self.credit - self.debit
        super(SupplierAccount, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.supplier} - {self.details}'

    @property
    def receipt_no(self):
        if self.tx_number.startswith('SP00-'):
            try:
                payment_id = int(self.tx_number.split('SP00-')[1])
                payment = Payment_To_Supplier.objects.filter(id=payment_id).first()
                if payment:
                    return payment.receipt_no
            except (ValueError, IndexError):
                pass
        return None


class Payment_To_Supplier(models.Model):
    CASH_IN_HAND = 'C/H'
    CASH_IN_BANK = 'C/B'
    CASH_ACCOUNTS_CHOICES = [
        (CASH_IN_HAND, 'CASH IN HAND'),
        (CASH_IN_BANK, 'CASH IN BANK')

    ]
    supplier = models.ForeignKey(
        Supplier, on_delete=models.PROTECT)
    paid_by = models.ForeignKey(
        Employment, on_delete=models.PROTECT)
    paid_from = models.CharField(
        max_length=3, choices=CASH_ACCOUNTS_CHOICES, default=CASH_IN_HAND)
    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    remain_amount = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    receipt_no = models.CharField(max_length=191, blank=True, null=True)
    remarks = models.TextField(blank=True, null=True)
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='supplier_payment_created', null=True)
    updated_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='supplier_payment_updated', null=True)
    created_at = models.DateTimeField(
        auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.supplier} - Payment: {self.id}'

    class Meta:
        verbose_name = 'Supplier Payment'
        verbose_name_plural = 'Supplier Payments'

    def save(self, *args, **kwargs):
        with transaction.atomic():
            from finance.models import CashAccount

            # Paying the supplier reduces our payable balance, so treat the
            # payment as a debit entry
            debit = self.amount_paid
            credit = 0

            previous_balance = (
                SupplierAccount.objects.filter(supplier=self.supplier)
                .order_by('-id')
                .values('current_balance')
                .first()
            )
            previous_balance = (
                previous_balance['current_balance'] if previous_balance else 0
            )

            current_balance = previous_balance + credit - debit
            self.remain_amount = current_balance

            # Save once to obtain an ID for tx_number references
            super().save(*args, **kwargs)

            amount_in = 0
            amount_out = self.amount_paid
            currency_instance = Currency.objects.first()
            current_date = date.today()

            ca_previous_balance = (
                CashAccount.objects.filter(account_type=self.paid_from)
                .order_by('-id')
                .values('current_balance')
                .first()
            )
            ca_previous_balance = (
                ca_previous_balance['current_balance'] if ca_previous_balance else 0
            )

            ca_current_balance = ca_previous_balance + amount_in - amount_out

            existing_cash_account = CashAccount.objects.filter(
                tx_number=f'SP00-{self.id}'
            ).first()

            if existing_cash_account:
                existing_cash_account.date = current_date
                existing_cash_account.amount_out = self.amount_paid
                existing_cash_account.previous_balance = ca_previous_balance
                existing_cash_account.current_balance = ca_current_balance
                existing_cash_account.save()
            else:
                cash_account = CashAccount(
                    date=current_date,
                    account_type=self.paid_from,
                    tx_number=f'SP00-{self.id}',
                    description=f'Supplier Payment #{self.id}',
                    amount_in=0,
                    amount_out=self.amount_paid,
                    previous_balance=ca_previous_balance,
                    current_balance=ca_current_balance,
                    currency=currency_instance,
                    remarks=(
                        f'Cash account entry for supplier {self.supplier} payment!'
                    ),
                )
                cash_account.save()

            details = f'Payment Voucher #{self.id}'
            tx_number = f'SP00-{self.id}'

            SupplierAccount.objects.update_or_create(
                supplier=self.supplier,
                tx_number=tx_number,
                defaults={
                    'details': details,
                    'previous_balance': previous_balance,
                    'debit': debit,
                    'credit': credit,
                    'current_balance': current_balance,
                },
            )

class ShareHolders(models.Model):
    name = models.CharField(max_length=191)
    contact = models.CharField(max_length=191)

    def __str__(self):
        return self.name


class Doctor(models.Model):
    name = models.CharField(max_length=191)
    contact = models.CharField(max_length=191)
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='doctor_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='doctor_updated',
        null=True,
        blank=True
    )

    def __str__(self):
        return f'Dr: {self.name}'


class DoctorCommission(models.Model):
    placed_at = models.DateField(auto_now=True)
    doctor = models.ForeignKey(Doctor, on_delete=models.PROTECT, null=True, blank=True)
    details = models.CharField(max_length=191, default='Null')
    previous_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    debit = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    credit = models.DecimalField(max_digits=10, decimal_places=2, default=0)
    current_balance = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    remarks = models.CharField(max_length=191, default='no remarks')

    def __str__(self):
        return f'{self.doctor} : {self.details}'

class DoctorProductCommission(models.Model):
    doctor = models.ForeignKey(Doctor, on_delete=models.PROTECT, null=True, blank=True)
    product = models.ForeignKey(Product, on_delete=models.PROTECT)
    commission = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    class Meta:
        unique_together = ('doctor', 'product')
        verbose_name = 'Doctor Product Commission'
        verbose_name_plural = 'Doctor Product Commissions'

    def __str__(self):
        return f"{self.doctor} - {self.product}: {self.commission}"


class Order(models.Model):
    STATUS_PENDING = 'P'
    STATUS_COMPLETED = 'C'
    STATUS_CHOICES = [
        (STATUS_PENDING, 'Pending'),
        (STATUS_COMPLETED, 'Completed')
    ]
    placed_at = models.DateTimeField(
        auto_now_add=True)
    order_status = models.CharField(
        max_length=1, choices=STATUS_CHOICES, default=STATUS_PENDING)
    order_by = models.CharField(max_length=191)
    order_to = models.ForeignKey(Supplier, on_delete=models.PROTECT, default=1)
    due_date = models.DateField(blank=True, null=True)
    cargo = models.CharField(max_length=191)
    other_charges = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    holder = models.ForeignKey(ShareHolders, on_delete=models.PROTECT)
    total_amount = models.DecimalField(
        decimal_places=2, max_digits=10, default=0)
    currency = models.ForeignKey(Currency, on_delete=models.PROTECT)
    exchange_rate = models.DecimalField(
        max_digits=10, decimal_places=4, default=0)
    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    amount_remain = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    remarks = models.TextField(
        blank=True, null=True, default='No Remarks!')
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='order_created', null=True)
    updated_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='order_updated', null=True)
    created_at = models.DateTimeField(
        auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'Order #: {self.id}'
    @property
    def supplier_returned_quantity(self):
        return (
            SupplierReturnItem.objects
            .filter(supplier_return__receive_order__order=self)
            .aggregate(total=Sum('quantity'))['total']
            or 0
        )

    @property
    def has_supplier_returns(self):
        return self.supplier_returned_quantity > 0
    
    @property
    def is_fully_returned(self):
        receive_order = self.receiveorder_set.first()
        if not receive_order:
            return False
        total_qty = (
            receive_order.receivedorderitem_set
            .aggregate(total=Sum('received_quantity'))['total']
            or 0
        )
        return total_qty > 0 and self.supplier_returned_quantity >= total_qty

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)  # Save Order first

        if self.pk:
            receive_order, _ = ReceiveOrder.objects.get_or_create(
                order=self,
                defaults={
                    'placed_at': self.placed_at,
                    'remarks': self.remarks,
                    'created_by': self.created_by,
                    'updated_by': self.updated_by,
                }
            )

            for order_item in self.orderitem_set.all():
                # Only create ReceivedOrderItem if it doesn't exist
                ReceivedOrderItem.objects.get_or_create(
                    received_order=receive_order,
                    product=order_item.product,
                    defaults={
                        'ordered_quantity': order_item.quantity,
                        'purchased_price': order_item.unit_price * self.exchange_rate,
                        'received_quantity': 0,
                        'damaged_quantity': 0,
                    }
                )


class OrderItem(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    product = models.ForeignKey(
        Product, on_delete=models.PROTECT, related_name='orderitems')
    quantity = models.PositiveSmallIntegerField()
    unit_price = models.DecimalField(max_digits=10, decimal_places=2)
    remarks = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True, null=True, blank=True)
    updated_at = models.DateTimeField(auto_now=True,    null=True, blank=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='order_item_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='order_item_updated',
        null=True,
        blank=True
    )

    class Meta:
        unique_together = ('order', 'product')

    def __str__(self):
        return f"{self.product}"

    @property
    def total_price(self):
        return self.quantity * self.unit_price


class ReceiveOrder(models.Model):
    order = models.ForeignKey(Order, on_delete=models.CASCADE)
    placed_at = models.DateTimeField(auto_now_add=True)
    remarks = models.TextField(
        blank=True, null=True, default='No Remarks!')
    created_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='receive_order_created', null=True)
    updated_by = models.ForeignKey(
        User, on_delete=models.SET_NULL, related_name='receive_order_updated', null=True)
    created_at = models.DateTimeField(
        auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f'{self.order}'

    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)

        # Update or create ProductBatches instances from the ReceivedOrderItems of the ReceiveOrder
        for order_item in self.receivedorderitem_set.all():
            received_quantity = order_item.received_quantity

            # Check if received_quantity is greater than 0
            if received_quantity > 0:
                product = order_item.product
                batch_number = order_item.batch_details
                purchased_rate = order_item.get_GP_Price()
                expiry_date = order_item.expiry_date
                remarks = f'From Order #:{self.order.id}'

                # Try to get an existing ProductBatches instance with the same batch_number and product
                existing_batch = ProductBatches.objects.filter(
                    product=product,
                    batch_number=batch_number
                ).first()

                if existing_batch:
                    # If a matching record exists, update its quantity and remarks
                    existing_batch.purchased_price = purchased_rate
                    existing_batch.remarks = remarks
                    existing_batch.save()
                else:
                    # If no matching record exists, create a new ProductBatches instance
                    product_batches = ProductBatches.objects.create(
                        product=product,
                        batch_number=batch_number,
                        expiry_date=expiry_date,
                        purchased_price=purchased_rate,
                        quantity=received_quantity,
                        remarks=remarks,
                        # ... (other fields as needed)
                    )


class ReceivedOrderItem(models.Model):
    received_order = models.ForeignKey(
        ReceiveOrder, on_delete=models.CASCADE)
    product = models.ForeignKey(
        Product, on_delete=models.PROTECT, related_name='receivedorderitems')
    batch_details = models.CharField(max_length=191, default='ABC001')
    expiry_date = models.DateField(default='2026-01-01')
    ordered_quantity = models.PositiveSmallIntegerField()
    purchased_price = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    received_quantity = models.PositiveIntegerField()
    damaged_quantity = models.PositiveIntegerField()
    carge_charges = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    sale_rate = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    def get_GP_Price(self):
        return self.carge_charges + self.purchased_price

    def clean(self):
        if self.received_quantity > self.ordered_quantity:
            raise ValidationError(
                f'Received quantity cannot be greater than ordered quantity. Ordered quantity: {self.ordered_quantity}'
            )

    def __str__(self):
        return str(self.product)

    def save(self, *args, **kwargs):
        # Update the product's unit_price and inventory
        if self.sale_rate != 0:
            self.product.unit_price = self.sale_rate
        self.product.inventory += self.received_quantity

        # Save the updated product
        self.product.save()

        super().save(*args, **kwargs)


class SaleOrder(models.Model):
    PAYMENT_STATUS_CASH = 'Cash'
    PAYMENT_STATUS_BARROW = 'Credi'
    PAYMENT_STATUS_CHOICES = [
        (PAYMENT_STATUS_CASH, 'CASH'),
        (PAYMENT_STATUS_BARROW, 'Credit')
    ]

    consumer = models.ForeignKey('Consumer', on_delete=models.PROTECT)
    transaction_id = models.UUIDField(default=uuid4, unique=True, editable=False)
    payment_status = models.CharField(max_length=50, choices=PAYMENT_STATUS_CHOICES, default=PAYMENT_STATUS_CASH)
    visited_by = models.ForeignKey(Employment, on_delete=models.PROTECT)
    remarks = models.TextField(blank=True, null=True)
    created_by = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='saleorder_created', null=True)
    updated_by = models.ForeignKey(User, on_delete=models.SET_NULL, related_name='saleorder_updated', null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    brt_rate = models.DecimalField(max_digits=5, decimal_places=2, default=0)

    def __str__(self):
        return f'Invoice #:{self.id} to {self.consumer}'

    class Meta:
        permissions = [
            ("sale_report", "Can view Sale Report"),
            ("stock_report", "Can view Stock Report"),
            ("saleorder_bulk_delete", "Can see bulk delete Sale Orders?"),
            ("saleorder_bulk_excel_download", "Can download Sale Orders in Excel?"),
            ("saleorder_bulk_pdf_download", "Can download Sale Orders in PDF?"),
            ("saleorder_print", "Can print Sale Orders?"),
        ]

    @property
    def brt_amount(self):
        base_total = sum(item.total_amount for item in self.saleorderitems_set.all())
        if not self.brt_rate:
            return Decimal(0)
        return (base_total * self.brt_rate) / Decimal(100)

    @property
    def grand_total(self):
        base_total = sum(item.total_amount for item in self.saleorderitems_set.all())
        return base_total - self.brt_amount

    @property
    def returned_quantity(self):
        return (
            ConsumerReturnItem.objects
            .filter(consumer_return__saleorder=self)
            .aggregate(total=Sum('quantity'))['total']
            or Decimal(0)
        )

    @property
    def has_returns(self):
        return self.returned_quantity > 0

    @property
    def is_fully_returned(self):
        total_qty = (
            self.saleorderitems_set
            .aggregate(total=Sum('quantity'))['total']
            or Decimal(0)
        )
        return total_qty > 0 and self.returned_quantity >= total_qty
        
class SaleOrderItems(models.Model):
    saleorder = models.ForeignKey(
        SaleOrder, on_delete=models.CASCADE)
    product = models.ForeignKey(
        ProductBatches, on_delete=models.PROTECT)
    quantity = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    doctor = models.ForeignKey(Doctor, on_delete=models.PROTECT, null=True, blank=True)
    commission = models.DecimalField(
        max_digits=10, decimal_places=2, default=0)
    discount = models.PositiveIntegerField(default=0)
    price = models.DecimalField(
        max_digits=10, decimal_places=2)
    net_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    # def clean(self):
    #     if self.quantity > self.product.product.inventory:
    #         raise ValidationError(
    #             f'Quantity cannot be greater than the available inventory. Current inventory: {self.product.product.inventory}'
    #         )

    @property
    def total_amount(self):
        return self.quantity * self.net_price

    @property
    def returned_quantity(self):
        return (
            self.consumerreturnitem_set.aggregate(total=Sum('quantity'))['total']
            or Decimal(0)
        )

    @property
    def is_returned(self):
        return self.returned_quantity >= self.quantity
    
    def save(self, *args, **kwargs):
        creating = not self.pk

        product_obj = self.product.product

        # Use doctor specific commission if defined
        try:
            mapping = DoctorProductCommission.objects.get(
                doctor=self.doctor,
                product=product_obj,
            )
            self.commission = mapping.commission
        except DoctorProductCommission.DoesNotExist:
            if not self.commission:
                self.commission = product_obj.commission

        # Apply consumer specific discount and price if defined
        if self.saleorder_id:
            try:
                dmap = ConsumerProductDiscount.objects.get(
                    consumer=self.saleorder.consumer,
                    product=product_obj,
                )
                if self.discount == 0:
                    self.discount = dmap.discount
                if dmap.unit_price is not None:
                    self.price = dmap.unit_price
            except ConsumerProductDiscount.DoesNotExist:
                pass

        # Use provided price if available otherwise default to product unit price
        if not self.price:
            self.price = product_obj.unit_price

        # Calculate net price per unit based on discount
        if self.discount <= 0:
            self.net_price = self.price
        else:
            discount_amount = self.price / 100 * self.discount
            self.net_price = self.price - discount_amount

        if creating:
            # Update the product's inventory only when creating
            self.product.product.inventory -= self.quantity
            self.product.quantity -= self.quantity

            # Save the updated product
            self.product.product.save()
            self.product.save()

        # Validate and save
        self.full_clean()
        super().save(*args, **kwargs)

    def __str__(self):
        return str(self.product)

class ConsumerReturn(models.Model):
    saleorder = models.ForeignKey(SaleOrder, on_delete=models.PROTECT)
    remarks = models.CharField(max_length=191, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True,blank=True,null=True)
    updated_at = models.DateTimeField(auto_now=True, blank=True,null=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_return_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='consumer_return_updated',
        null=True,
        blank=True
    )

    def __str__(self):
        return f'Return #{self.id} for SO {self.saleorder.id}'

    def doctor_commission_totals(self):
        totals = defaultdict(Decimal)
        for item in self.consumerreturnitem_set.all():
            totals[item.saleorder_item.doctor_id] += item.saleorder_item.commission * item.quantity
        return totals

    def total_amount(self):
        return sum(item.return_amount for item in self.consumerreturnitem_set.all())

class ConsumerReturnItem(models.Model):
    consumer_return = models.ForeignKey(ConsumerReturn, on_delete=models.CASCADE)
    saleorder_item = models.ForeignKey(SaleOrderItems, on_delete=models.PROTECT)
    quantity = models.DecimalField(max_digits=10, decimal_places=2, default=0)

    @property
    def return_amount(self):
        return self.quantity * self.saleorder_item.net_price

    def __str__(self):
        return f'{self.quantity} of {self.saleorder_item}'

class SupplierReturn(models.Model):
    receive_order = models.ForeignKey(ReceiveOrder, on_delete=models.PROTECT)
    remarks = models.CharField(max_length=191, null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True)
    updated_at = models.DateTimeField(auto_now=True, blank=True, null=True)
    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='supplier_return_created',
        null=True,
        blank=True
    )
    updated_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        related_name='supplier_return_updated',
        null=True,
        blank=True
    )

    def __str__(self):
        return f'Return #{self.id} for PO {self.receive_order.order.id}'

class SupplierReturnItem(models.Model):
    supplier_return = models.ForeignKey(SupplierReturn, on_delete=models.CASCADE)
    received_item = models.ForeignKey(ReceivedOrderItem, on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField(default=0)

    def __str__(self):
        return f'{self.quantity} of {self.received_item.product}'
