from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Sum
from django.utils import timezone

from shop_management.models.data.inventory import InventoryBatch, StockMovement
from shop_management.models.data.purchase_return import PurchaseReturn, PurchaseReturnItem
from shop_management.services.inventory import issue_stock_batch, receive_stock
from shop_management.services.automatic_journals import create_system_journal, void_system_journal


def _number(document):
    prefix = f"{document.shop.name[:3].upper()}-PRT-{timezone.localdate().year}-"
    last = PurchaseReturn.objects.filter(return_number__startswith=prefix).order_by("-return_number").values_list("return_number", flat=True).first()
    sequence = int(last.rsplit("-", 1)[-1]) + 1 if last else 1
    return f"{prefix}{sequence:06d}"


@transaction.atomic
def post_purchase_return(return_id, *, user):
    document = PurchaseReturn.objects.select_for_update().select_related("purchase__warehouse", "purchase__currency", "shop").get(pk=return_id)
    if not document.uses_inventory_engine or document.document_status != PurchaseReturn.DocumentStatus.DRAFT:
        raise ValidationError("Only draft new-engine purchase returns can be posted.")
    if document.purchase.document_status != document.purchase.DocumentStatus.POSTED:
        raise ValidationError({"purchase": "The original purchase must be posted."})
    items = list(document.return_items.select_related("purchase_item__product", "batch"))
    if not items:
        raise ValidationError({"return_items": "At least one return item is required."})
    for row in items:
        item = row.purchase_item
        if item.purchase_id != document.purchase_id:
            raise ValidationError({"return_items": "Every item must belong to the original purchase."})
        returned = PurchaseReturnItem.objects.filter(
            purchase_item=item, purchase_return__document_status=PurchaseReturn.DocumentStatus.POSTED
        ).exclude(purchase_return=document).aggregate(total=Sum("return_quantity"))["total"] or 0
        if int(returned) + row.return_quantity > item.quantity:
            raise ValidationError({"return_items": f"Return quantity exceeds the remaining quantity for {item.product}."})
        batch = row.batch
        if not batch:
            batch = InventoryBatch.objects.filter(product=item.product, batch_number=item.batch_number).first()
        if not batch:
            raise ValidationError({"return_items": f"Inventory batch is required for {item.product}."})
        issue_stock_batch(
            warehouse=document.purchase.warehouse, product=item.product, batch=batch,
            quantity=row.return_quantity, movement_type=StockMovement.MovementType.PURCHASE_RETURN,
            reference_type="PurchaseReturn", reference_id=document.pk,
            remarks=document.reason or "", created_by=user,
        )
        if row.batch_id != batch.id:
            row.batch = batch
            row.save(update_fields=["batch", "updated_at"])
    document.return_number = document.return_number or _number(document)
    document.document_status = PurchaseReturn.DocumentStatus.POSTED
    document.posted_at = timezone.now()
    document.posted_by = user
    document.save(update_fields=["return_number", "document_status", "posted_at", "posted_by", "updated_at"])
    total = sum((row.refund_amount for row in items), Decimal("0"))
    create_system_journal(shop=document.shop, entry_date=document.return_date.date(), source_type="PURCHASE_RETURN", source_id=document.pk,
        description=f"Purchase return {document.return_number}", currency=document.purchase.currency, exchange_rate=document.purchase.exchange_rate_used or 1, user=user,
        lines=[{"code": "2100", "side": "debit", "amount": total, "supplier": document.purchase.supplier}, {"code": "1500", "side": "credit", "amount": total, "supplier": document.purchase.supplier}])
    return document


@transaction.atomic
def void_purchase_return(return_id, *, user, reason):
    document = PurchaseReturn.objects.select_for_update().select_related("purchase__warehouse", "purchase__currency").get(pk=return_id)
    if document.document_status != PurchaseReturn.DocumentStatus.POSTED or not document.uses_inventory_engine:
        raise ValidationError("Only posted new-engine purchase returns can be cancelled.")
    if not str(reason or "").strip():
        raise ValidationError({"void_reason": "A cancellation reason is required."})
    for row in document.return_items.select_related("purchase_item__product", "batch"):
        receive_stock(
            warehouse=document.purchase.warehouse, product=row.purchase_item.product, batch=row.batch,
            quantity=row.return_quantity, unit_cost=row.purchase_item.buy_price,
            currency=document.purchase.currency, movement_type=StockMovement.MovementType.VOID_REVERSAL,
            reference_type="PurchaseReturnVoid", reference_id=document.pk,
            remarks=str(reason).strip(), created_by=user,
        )
    document.document_status = PurchaseReturn.DocumentStatus.VOID
    document.voided_at = timezone.now()
    document.voided_by = user
    document.void_reason = str(reason).strip()
    document.save(update_fields=["document_status", "voided_at", "voided_by", "void_reason", "updated_at"])
    void_system_journal(shop=document.shop, source_type="PURCHASE_RETURN", source_id=document.pk, user=user, reason=str(reason).strip())
    return document
