"""
expense/signals.py
==================
Budget allocation tracking: update committed/approved/disbursed amounts
whenever an ExpenseRequest status changes.
"""

from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.db.models import F

from .models import (
    STATUS_PENDING,
    STATUS_APPROVED,
    STATUS_DISBURSED,
    STATUS_KIV,
    STATUS_RESUBMITTED,
)


def _update_allocation(alloc, remove_from, add_to, amount):
    """
    Move `amount` from one bucket to another on a BudgetAllocation.
    Uses F() expressions to avoid race conditions.
    """
    if not alloc:
        return

    update_fields = []

    if remove_from == "committed":
        alloc.committed_amount = F("committed_amount") - amount
        update_fields.append("committed_amount")
    elif remove_from == "approved":
        alloc.approved_amount = F("approved_amount") - amount
        update_fields.append("approved_amount")

    if add_to == "committed":
        alloc.committed_amount = F("committed_amount") + amount
        update_fields.append("committed_amount")
    elif add_to == "approved":
        alloc.approved_amount = F("approved_amount") + amount
        update_fields.append("approved_amount")
    elif add_to == "disbursed":
        alloc.disbursed_amount = F("disbursed_amount") + amount
        update_fields.append("disbursed_amount")

    if update_fields:
        alloc.save(update_fields=list(set(update_fields)))


@receiver(pre_save, sender="expense.ExpenseRequest")
def sync_budget_allocation(sender, instance, **kwargs):
    """
    On every save of an ExpenseRequest, detect a status change and
    move the amount between budget allocation buckets accordingly.
    """
    if not instance.pk:
        # New record — will be handled post-save via explicit call from the view
        return

    try:
        old = sender.objects.get(pk=instance.pk)
    except sender.DoesNotExist:
        return

    old_status = old.status
    new_status = instance.status

    if old_status == new_status:
        return

    alloc = instance.get_current_allocation()
    amount = instance.amount

    # Determine which bucket to remove from (based on old status)
    remove_from = None
    if old_status in (STATUS_PENDING, STATUS_RESUBMITTED, STATUS_KIV):
        remove_from = "committed"
    elif old_status == STATUS_APPROVED:
        remove_from = "approved"
    # rejected/closed/disbursed: already removed, nothing to undo

    # Determine which bucket to add to (based on new status)
    add_to = None
    if new_status in (STATUS_PENDING, STATUS_RESUBMITTED, STATUS_KIV):
        add_to = "committed"
    elif new_status == STATUS_APPROVED:
        add_to = "approved"
    elif new_status == STATUS_DISBURSED:
        add_to = "disbursed"
    # rejected/closed: just remove, no new bucket

    _update_allocation(alloc, remove_from, add_to, amount)
