from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from django.utils import timezone

from shop_management.models.base import DataRoot
from shop_management.models.data.organization import Warehouse
from shop_management.models.data.product import Product
from shop_management.models.data.utils import Currency


class InventoryBatch(DataRoot):
    product = models.ForeignKey(
        Product, related_name="batches", on_delete=models.PROTECT
    )
    batch_number = models.CharField(max_length=100)
    manufacture_date = models.DateField(null=True, blank=True)
    expiry_date = models.DateField(null=True, blank=True, db_index=True)
    is_blocked = models.BooleanField(default=False)
    notes = models.TextField(blank=True)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["product", "batch_number"], name="uniq_batch_per_product"
            )
        ]
        ordering = ["product_id", "expiry_date", "id"]

    def clean(self):
        super().clean()
        if (
            self.manufacture_date
            and self.expiry_date
            and self.expiry_date < self.manufacture_date
        ):
            raise ValidationError(
                {"expiry_date": "Expiry date cannot be before manufacture date."}
            )

    @property
    def is_expired(self):
        return bool(self.expiry_date and self.expiry_date < timezone.localdate())

    def save(self, *args, **kwargs):
        self.batch_number = self.batch_number.strip()
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.product} - {self.batch_number}"


class StockBalance(DataRoot):
    warehouse = models.ForeignKey(
        Warehouse, related_name="stock_balances", on_delete=models.PROTECT
    )
    product = models.ForeignKey(
        Product, related_name="stock_balances", on_delete=models.PROTECT
    )
    batch = models.ForeignKey(
        InventoryBatch, related_name="stock_balances", on_delete=models.PROTECT
    )
    quantity = models.DecimalField(max_digits=18, decimal_places=4, default=0)
    average_cost = models.DecimalField(max_digits=18, decimal_places=6, default=0)
    cost_currency = models.ForeignKey(
        Currency, related_name="stock_balances", on_delete=models.PROTECT
    )

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["warehouse", "product", "batch"],
                name="uniq_stock_balance",
            ),
            models.CheckConstraint(
                check=models.Q(quantity__gte=0), name="stock_quantity_nonnegative"
            ),
            models.CheckConstraint(
                check=models.Q(average_cost__gte=0), name="stock_average_cost_nonnegative"
            ),
        ]
        ordering = ["warehouse_id", "product_id", "batch__expiry_date", "id"]

    def clean(self):
        super().clean()
        errors = {}
        if self.batch_id and self.product_id and self.batch.product_id != self.product_id:
            errors["batch"] = "Batch must belong to the selected product."
        if self.warehouse_id and self.product_id and self.warehouse.shop_id != self.product.shop_id:
            errors["warehouse"] = "Warehouse must belong to the product branch."
        if self.cost_currency_id and self.product_id and self.cost_currency.shop_id != self.product.shop_id:
            errors["cost_currency"] = "Cost currency must belong to the product branch."
        if errors:
            raise ValidationError(errors)

    def save(self, *args, **kwargs):
        if self.quantity is not None and self.quantity < 0:
            raise ValidationError(
                {"quantity": "Stock cannot go below zero. Reduce the requested quantity."}
            )
        return super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.warehouse} - {self.product} - {self.batch.batch_number}"


class StockMovement(DataRoot):
    class MovementType(models.TextChoices):
        OPENING = "OPENING", "Opening"
        PURCHASE = "PURCHASE", "Purchase"
        PURCHASE_RETURN = "PURCHASE_RETURN", "Purchase Return"
        SALE = "SALE", "Sale"
        SALE_RETURN = "SALE_RETURN", "Sale Return"
        TRANSFER_OUT = "TRANSFER_OUT", "Transfer Out"
        TRANSFER_IN = "TRANSFER_IN", "Transfer In"
        ADJUSTMENT_IN = "ADJUSTMENT_IN", "Adjustment In"
        ADJUSTMENT_OUT = "ADJUSTMENT_OUT", "Adjustment Out"
        EXPIRY_WRITE_OFF = "EXPIRY_WRITE_OFF", "Expiry Write-off"
        VOID_REVERSAL = "VOID_REVERSAL", "Void Reversal"

    warehouse = models.ForeignKey(
        Warehouse, related_name="stock_movements", on_delete=models.PROTECT
    )
    product = models.ForeignKey(
        Product, related_name="stock_movements", on_delete=models.PROTECT
    )
    batch = models.ForeignKey(
        InventoryBatch, related_name="stock_movements", on_delete=models.PROTECT
    )
    movement_type = models.CharField(max_length=30, choices=MovementType.choices)
    quantity_in = models.DecimalField(max_digits=18, decimal_places=4, default=0)
    quantity_out = models.DecimalField(max_digits=18, decimal_places=4, default=0)
    unit_cost = models.DecimalField(max_digits=18, decimal_places=6)
    currency = models.ForeignKey(
        Currency, related_name="stock_movements", on_delete=models.PROTECT
    )
    movement_date = models.DateTimeField(default=timezone.now, db_index=True)
    reference_type = models.CharField(max_length=100, blank=True, db_index=True)
    reference_id = models.CharField(max_length=100, blank=True, db_index=True)
    remarks = models.TextField(blank=True)

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.Q(quantity_in__gte=0), name="movement_quantity_in_nonnegative"
            ),
            models.CheckConstraint(
                check=models.Q(quantity_out__gte=0), name="movement_quantity_out_nonnegative"
            ),
            models.CheckConstraint(
                check=models.Q(unit_cost__gte=0), name="movement_unit_cost_nonnegative"
            ),
            models.CheckConstraint(
                check=(
                    models.Q(quantity_in__gt=0, quantity_out=0)
                    | models.Q(quantity_out__gt=0, quantity_in=0)
                ),
                name="movement_has_one_direction",
            ),
        ]
        ordering = ["-movement_date", "-id"]

    def save(self, *args, **kwargs):
        if self.pk and StockMovement.objects.filter(pk=self.pk).exists():
            raise ValidationError("Stock movements are immutable.")
        return super().save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        raise ValidationError("Stock movements are immutable and cannot be deleted.")

    def __str__(self):
        return f"{self.movement_type} - {self.product} - {self.movement_date}"
