"""
Loan Calculation Service

Provides centralized calculation logic for loan financial metrics including
amount paid, outstanding amounts, daily payment requirements, and penalty tracking.

This service ensures consistent calculations across all report pages.
"""

from decimal import Decimal, InvalidOperation
from django.db.models import Sum
from django.utils import timezone
import logging

logger = logging.getLogger(__name__)


class LoanCalculationService:
    """
    Centralized service for loan financial calculations.
    
    Provides accurate calculations for:
    - Amount paid (sum of all repayments)
    - Outstanding amount (including penalties)
    - Daily payment required
    - Total amount with penalties
    - Days overdue
    """
    
    @staticmethod
    def calculate_amount_paid(loan) -> Decimal:
        """
        Calculate the total amount paid on a loan by summing all repayment records.
        
        Args:
            loan: Loan instance
            
        Returns:
            Decimal: Total amount paid, rounded to 2 decimal places. Returns 0 if no repayments exist or on error.
            
        Validates: Requirements 3.1, 3.3, 9.4, 9.5, 13.3
        """
        try:
            if not loan:
                return Decimal('0.00')
            
            # Sum all repayment amounts for this loan
            total_paid = loan.repayments.aggregate(
                total=Sum('amount')
            )['total']
            
            # Return 0 if no repayments exist
            result = total_paid if total_paid is not None else Decimal('0.00')
            
            # Round to 2 decimal places
            return result.quantize(Decimal('0.01'))
            
        except Exception as e:
            logger.error(f"Error calculating amount paid for loan {getattr(loan, 'id', 'unknown')}: {str(e)}")
            return Decimal('0.00')
    
    @staticmethod
    def calculate_outstanding_amount(loan) -> Decimal:
        """
        Calculate the outstanding amount on a loan.
        
        Outstanding = Total Amount + Total Penalties - Amount Paid
        
        Args:
            loan: Loan instance
            
        Returns:
            Decimal: Outstanding amount (never negative), rounded to 2 decimal places. Returns 0 on error.
            
        Validates: Requirements 3.4, 9.4, 9.5, 13.2
        """
        try:
            if not loan:
                return Decimal('0.00')
            
            # Get total loan amount (principal + interest + processing fee)
            # Use the stored total_amount, not calculated_total_amount
            try:
                total_amount = Decimal(str(loan.total_amount))
            except (InvalidOperation, ValueError, TypeError) as e:
                logger.warning(f"Invalid total_amount for loan {getattr(loan, 'id', 'unknown')}: {str(e)}")
                total_amount = Decimal('0.00')
            
            # Get total penalties
            total_penalties = LoanCalculationService.calculate_total_penalties(loan)
            
            # Get amount paid
            amount_paid = LoanCalculationService.calculate_amount_paid(loan)
            
            # Calculate outstanding: total + penalties - paid
            outstanding = total_amount + total_penalties - amount_paid
            
            # Never return negative outstanding
            result = max(outstanding, Decimal('0.00'))
            
            # Round to 2 decimal places
            return result.quantize(Decimal('0.01'))
            
        except Exception as e:
            logger.error(f"Error calculating outstanding amount for loan {getattr(loan, 'id', 'unknown')}: {str(e)}")
            return Decimal('0.00')
    
    @staticmethod
    def calculate_total_penalties(loan) -> Decimal:
        """
        Calculate total penalty charges applied to a loan.
        
        Args:
            loan: Loan instance
            
        Returns:
            Decimal: Total penalty amount, rounded to 2 decimal places
            
        Validates: Requirements 9.4, 9.5, 13.4
        """
        if not loan:
            return Decimal('0.00')
        
        # Sum all penalty charges for this loan
        total_penalties = loan.penalty_charges.aggregate(
            total=Sum('amount')
        )['total']
        
        result = total_penalties if total_penalties is not None else Decimal('0.00')
        
        # Round to 2 decimal places
        return result.quantize(Decimal('0.01'))
    
    @staticmethod
    def calculate_daily_payment_required(loan) -> Decimal:
        """
        Calculate the daily payment amount required to pay off the loan by due date.
        
        Daily Payment = Outstanding Amount / Remaining Days
        
        Args:
            loan: Loan instance
            
        Returns:
            Decimal: Daily payment required, rounded to 2 decimal places. Returns 0 if loan is overdue or paid.
            
        Validates: Requirements 3.5, 9.4, 9.5
        """
        if not loan:
            return Decimal('0.00')
        
        # Get outstanding amount
        outstanding = LoanCalculationService.calculate_outstanding_amount(loan)
        
        # If fully paid, no daily payment required
        if outstanding <= Decimal('0.00'):
            return Decimal('0.00')
        
        # Calculate remaining days until due date
        current_date = timezone.now().date()
        due_date = loan.due_date.date() if hasattr(loan.due_date, 'date') else loan.due_date
        
        remaining_days = (due_date - current_date).days
        
        # If overdue or due today, return full outstanding amount
        if remaining_days <= 0:
            return outstanding
        
        # Calculate daily payment required
        daily_payment = outstanding / Decimal(str(remaining_days))
        
        # Round to 2 decimal places
        return daily_payment.quantize(Decimal('0.01'))
    
    @staticmethod
    def calculate_total_with_penalties(loan) -> Decimal:
        """
        Calculate the total loan amount including all penalties.
        
        Total with Penalties = Total Amount + Total Penalties
        
        Args:
            loan: Loan instance
            
        Returns:
            Decimal: Total amount including penalties, rounded to 2 decimal places
            
        Validates: Requirements 9.4, 9.5, 13.1, 13.4
        """
        if not loan:
            return Decimal('0.00')
        
        # Get base total amount
        total_amount = Decimal(str(loan.total_amount))
        
        # Get total penalties
        total_penalties = LoanCalculationService.calculate_total_penalties(loan)
        
        result = total_amount + total_penalties
        
        # Round to 2 decimal places
        return result.quantize(Decimal('0.01'))
    
    @staticmethod
    def calculate_days_overdue(loan) -> int:
        """
        Calculate the number of days a loan is overdue.
        
        Days Overdue = Current Date - Due Date (if positive)
        
        Args:
            loan: Loan instance
            
        Returns:
            int: Number of days overdue. Returns 0 if not overdue.
            
        Validates: Requirements 14.2
        """
        if not loan:
            return 0
        
        # Get current date
        current_date = timezone.now().date()
        
        # Get due date
        due_date = loan.due_date.date() if hasattr(loan.due_date, 'date') else loan.due_date
        
        # Calculate days overdue
        days_overdue = (current_date - due_date).days
        
        # Return 0 if not overdue
        return max(days_overdue, 0)
    
    @staticmethod
    def format_currency(amount) -> str:
        """
        Format a currency amount with exactly two decimal places.
        
        Args:
            amount: Decimal or numeric value
            
        Returns:
            str: Formatted currency string with 2 decimal places. Returns "0.00" on error.
            
        Validates: Requirements 13.5
        """
        try:
            if amount is None:
                return "0.00"
            
            # Convert to Decimal if not already
            if not isinstance(amount, Decimal):
                try:
                    amount = Decimal(str(amount))
                except (InvalidOperation, ValueError, TypeError) as e:
                    logger.warning(f"Invalid amount for currency formatting: {amount}. Error: {str(e)}")
                    return "0.00"
            
            # Format with exactly 2 decimal places
            return f"{amount:.2f}"
            
        except Exception as e:
            logger.error(f"Error formatting currency: {str(e)}")
            return "0.00"
    
    @staticmethod
    def calculate_total_loan_amount(principal, interest, processing_fee) -> Decimal:
        """
        Calculate total loan amount from components with error handling.
        
        Total = Principal + Interest + Processing Fee
        
        Args:
            principal: Principal amount
            interest: Interest amount
            processing_fee: Processing fee amount
            
        Returns:
            Decimal: Total loan amount, rounded to 2 decimal places. Returns 0 on error.
            
        Validates: Requirements 9.4, 9.5, 13.1
        """
        try:
            # Convert each component to Decimal with error handling
            try:
                principal_dec = Decimal(str(principal)) if principal else Decimal('0.00')
            except (InvalidOperation, ValueError, TypeError):
                logger.warning(f"Invalid principal amount: {principal}")
                principal_dec = Decimal('0.00')
            
            try:
                interest_dec = Decimal(str(interest)) if interest else Decimal('0.00')
            except (InvalidOperation, ValueError, TypeError):
                logger.warning(f"Invalid interest amount: {interest}")
                interest_dec = Decimal('0.00')
            
            try:
                fee_dec = Decimal(str(processing_fee)) if processing_fee else Decimal('0.00')
            except (InvalidOperation, ValueError, TypeError):
                logger.warning(f"Invalid processing fee: {processing_fee}")
                fee_dec = Decimal('0.00')
            
            result = principal_dec + interest_dec + fee_dec
            
            # Round to 2 decimal places
            return result.quantize(Decimal('0.01'))
            
        except Exception as e:
            logger.error(f"Error calculating total loan amount: {str(e)}")
            return Decimal('0.00')
