from django.core.exceptions import ValidationError
from django.db import transaction
from django.utils import timezone

from shop_management.models.data.inventory import StockBalance, StockMovement
from shop_management.models.data.warehouse_transfer import WarehouseTransfer
from shop_management.services.inventory import issue_stock_batch, receive_stock


def _number(document):
    prefix = f"{document.shop.name[:3].upper()}-WTR-{document.transfer_date.year}-"
    last = WarehouseTransfer.objects.filter(transfer_number__startswith=prefix).order_by("-transfer_number").values_list("transfer_number", flat=True).first()
    sequence = int(last.rsplit("-", 1)[-1]) + 1 if last else 1
    return f"{prefix}{sequence:06d}"


def _validate(document):
    errors = {}
    if document.source_warehouse_id == document.destination_warehouse_id:
        errors["destination_warehouse"] = "Source and destination warehouses must be different."
    if document.source_warehouse.shop_id != document.shop_id or document.destination_warehouse.shop_id != document.shop_id:
        errors["warehouses"] = "Both warehouses must belong to the selected branch."
    items = list(document.items.select_related("product", "batch"))
    if not items:
        errors["items"] = "At least one transfer item is required."
    for index, item in enumerate(items):
        if item.quantity <= 0:
            errors[f"items[{index}]"] = "Quantity must be greater than zero."
        elif item.product.shop_id != document.shop_id or item.batch.product_id != item.product_id:
            errors[f"items[{index}]"] = "Product and batch must belong to the transfer branch."
    if errors:
        raise ValidationError(errors)
    return items


@transaction.atomic
def dispatch_transfer(transfer_id, *, user):
    document = WarehouseTransfer.objects.select_for_update().select_related("shop", "source_warehouse", "destination_warehouse").get(pk=transfer_id)
    if document.status != WarehouseTransfer.Status.DRAFT:
        raise ValidationError("Only draft transfers can be dispatched.")
    for item in _validate(document):
        _, movement = issue_stock_batch(
            warehouse=document.source_warehouse, product=item.product, batch=item.batch,
            quantity=item.quantity, movement_type=StockMovement.MovementType.TRANSFER_OUT,
            reference_type="WarehouseTransfer", reference_id=document.pk,
            remarks=document.notes, created_by=user, movement_date=timezone.now(),
        )
        item.unit_cost_snapshot = movement.unit_cost
        item.cost_currency = movement.currency
        item.save(update_fields=["unit_cost_snapshot", "cost_currency", "updated_at"])
    document.transfer_number = document.transfer_number or _number(document)
    document.status = WarehouseTransfer.Status.DISPATCHED
    document.dispatched_at = timezone.now()
    document.dispatched_by = user
    document.save(update_fields=["transfer_number", "status", "dispatched_at", "dispatched_by", "updated_at"])
    return document


@transaction.atomic
def receive_transfer(transfer_id, *, user):
    document = WarehouseTransfer.objects.select_for_update().select_related("destination_warehouse").get(pk=transfer_id)
    if document.status != WarehouseTransfer.Status.DISPATCHED:
        raise ValidationError("Only dispatched transfers can be received.")
    for item in document.items.select_related("product", "batch", "cost_currency"):
        receive_stock(
            warehouse=document.destination_warehouse, product=item.product, batch=item.batch,
            quantity=item.quantity, unit_cost=item.unit_cost_snapshot, currency=item.cost_currency,
            movement_type=StockMovement.MovementType.TRANSFER_IN,
            reference_type="WarehouseTransfer", reference_id=document.pk,
            remarks=document.notes, created_by=user,
        )
    document.status = WarehouseTransfer.Status.RECEIVED
    document.received_at = timezone.now()
    document.received_by = user
    document.save(update_fields=["status", "received_at", "received_by", "updated_at"])
    return document


@transaction.atomic
def void_transfer(transfer_id, *, user, reason):
    document = WarehouseTransfer.objects.select_for_update().select_related("source_warehouse", "destination_warehouse").get(pk=transfer_id)
    if document.status == WarehouseTransfer.Status.VOID:
        raise ValidationError("Transfer is already cancelled.")
    if not str(reason or "").strip():
        raise ValidationError({"void_reason": "A cancellation reason is required."})
    items = list(document.items.select_related("product", "batch", "cost_currency"))
    if document.status == WarehouseTransfer.Status.RECEIVED:
        for item in items:
            issue_stock_batch(
                warehouse=document.destination_warehouse, product=item.product, batch=item.batch,
                quantity=item.quantity, movement_type=StockMovement.MovementType.VOID_REVERSAL,
                reference_type="WarehouseTransferVoidDestination", reference_id=document.pk,
                remarks=str(reason).strip(), created_by=user,
            )
    if document.status in (WarehouseTransfer.Status.DISPATCHED, WarehouseTransfer.Status.RECEIVED):
        for item in items:
            receive_stock(
                warehouse=document.source_warehouse, product=item.product, batch=item.batch,
                quantity=item.quantity, unit_cost=item.unit_cost_snapshot, currency=item.cost_currency,
                movement_type=StockMovement.MovementType.VOID_REVERSAL,
                reference_type="WarehouseTransferVoidSource", reference_id=document.pk,
                remarks=str(reason).strip(), created_by=user,
            )
    document.status = WarehouseTransfer.Status.VOID
    document.voided_at = timezone.now()
    document.voided_by = user
    document.void_reason = str(reason).strip()
    document.save(update_fields=["status", "voided_at", "voided_by", "void_reason", "updated_at"])
    return document
