from django.db.models.signals import post_save, pre_save
from django.dispatch import receiver
from django.db import models
from decimal import Decimal

@receiver(post_save, sender='loans.Repayment')
def create_receipt_for_repayment(sender, instance, created, **kwargs):
    """Automatically create receipt when repayment is saved"""
    if created:
        from utils.models import Receipt
        
        # Check if receipt already exists
        try:
            if hasattr(instance, 'receipt') and instance.receipt:
                return
        except Receipt.DoesNotExist:
            pass
        
        try:
            # Calculate balances
            loan = instance.loan
            
            # Get all repayments before this one for the same loan
            previous_total = sender.objects.filter(
                loan=loan,
                payment_date__lt=instance.payment_date
            ).aggregate(total=models.Sum('amount'))['total']
            
            # Ensure previous_total is Decimal (Sum can return float)
            if previous_total is None:
                previous_total = Decimal('0')
            elif not isinstance(previous_total, Decimal):
                previous_total = Decimal(str(previous_total))
            
            previous_balance = loan.total_amount - previous_total
            new_balance = previous_balance - instance.amount
            
            # Create receipt
            Receipt.objects.create(
                repayment=instance,
                loan=loan,
                borrower=loan.borrower,
                amount_paid=instance.amount,
                payment_method=instance.payment_method,
                payment_date=instance.payment_date,
                previous_balance=previous_balance,
                new_balance=new_balance
            )
            
            print(f"SUCCESS: Created receipt for repayment {instance.receipt_number}")
            
        except Exception as e:
            print(f"ERROR: Error creating receipt for repayment {instance.id}: {e}")


@receiver(post_save, sender='loans.Repayment')
def recalculate_loan_on_payment(sender, instance, created, **kwargs):
    """
    Trigger recalculation when payment is recorded.
    
    Validates: Requirements 9.6, 9.7, 9.8
    """
    # Avoid infinite recursion by checking if we're already in a save operation
    if hasattr(instance, '_recalculating'):
        return
    
    try:
        # Mark that we're recalculating to avoid recursion
        instance._recalculating = True
        
        # Update loan's amount_paid cache
        from django.db.models import Sum
        loan = instance.loan
        total_paid = loan.repayments.aggregate(
            total=Sum('amount')
        )['total'] or Decimal('0.00')
        
        loan._amount_paid_cache = total_paid
        loan.last_payment_date = instance.payment_date
        
        # Recalculate amounts (this will update total_amount and outstanding_amount)
        # Note: outstanding_amount is a property, so it's automatically recalculated
        loan.save(update_fields=['_amount_paid_cache', 'last_payment_date'])
        
    except Exception as e:
        print(f"ERROR: Error recalculating loan amounts after payment: {e}")
    finally:
        # Clean up the flag
        if hasattr(instance, '_recalculating'):
            delattr(instance, '_recalculating')


@receiver(post_save, sender='loans.PenaltyCharge')
def recalculate_loan_on_penalty(sender, instance, created, **kwargs):
    """
    Trigger recalculation when penalty is added.
    
    Validates: Requirements 9.6, 9.7, 9.8
    """
    # Avoid infinite recursion
    if hasattr(instance, '_recalculating'):
        return
    
    try:
        # Mark that we're recalculating
        instance._recalculating = True
        
        # Recalculate loan amounts
        # Note: outstanding_amount is a property that includes penalties,
        # so it will be automatically recalculated when accessed
        loan = instance.loan
        
        # Just touch the loan to update the updated_at timestamp
        # The outstanding_amount property will automatically include the new penalty
        loan.save(update_fields=['updated_at'])
        
    except Exception as e:
        print(f"ERROR: Error recalculating loan amounts after penalty: {e}")
    finally:
        if hasattr(instance, '_recalculating'):
            delattr(instance, '_recalculating')


@receiver(pre_save, sender='loans.Loan')
def recalculate_loan_on_edit(sender, instance, **kwargs):
    """
    Trigger recalculation when loan amounts are edited.
    
    This signal checks if principal_amount, interest_amount, or processing_fee
    have changed, and if so, recalculates total_amount.
    
    Validates: Requirements 9.6, 9.7, 9.8
    """
    # Avoid infinite recursion
    if hasattr(instance, '_recalculating'):
        return
    
    # Only recalculate for existing loans (not new ones)
    if not instance.pk:
        return
    
    try:
        # Get the old instance from database
        old_instance = sender.objects.get(pk=instance.pk)
        
        # Check if any amount fields have changed
        amounts_changed = (
            old_instance.principal_amount != instance.principal_amount or
            old_instance.interest_amount != instance.interest_amount or
            old_instance.processing_fee != instance.processing_fee
        )
        
        if amounts_changed:
            # Mark that we're recalculating
            instance._recalculating = True
            
            # Recalculate total_amount
            instance.total_amount = (
                instance.principal_amount + 
                instance.interest_amount + 
                instance.processing_fee
            )
            
            # Round to 2 decimal places
            instance.total_amount = instance.total_amount.quantize(Decimal('0.01'))
            
    except sender.DoesNotExist:
        # New loan, no need to recalculate
        pass
    except Exception as e:
        print(f"ERROR: Error recalculating loan amounts on edit: {e}")
    finally:
        if hasattr(instance, '_recalculating'):
            delattr(instance, '_recalculating')