from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from django.utils import timezone
from django.contrib.auth import get_user_model
import uuid
from decimal import Decimal

User = get_user_model()


class LoanProduct(models.Model):
    """
    Loan product definitions with interest rates and terms
    """
    PRODUCT_TYPES = [
        ('boost', 'Boost'),
        ('mwamba', 'Mwamba'),
        ('imara', 'Imara')
    ]
    
    REPAYMENT_METHODS = [
        ('daily', 'Daily'),
        ('weekly', 'Weekly'),
        ('monthly', 'Monthly'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=200)
    product_type = models.CharField(max_length=20, choices=PRODUCT_TYPES)
    description = models.TextField()
    
    # Loan amounts
    min_amount = models.DecimalField(max_digits=12, decimal_places=2)
    max_amount = models.DecimalField(max_digits=12, decimal_places=2)
    
    # Interest and fees
    interest_rate = models.DecimalField(
        max_digits=5, 
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        help_text="Monthly interest rate in percentage"
    )
    processing_fee = models.DecimalField(
        max_digits=5, 
        decimal_places=2,
        validators=[MinValueValidator(0), MaxValueValidator(100)],
        help_text="Processing fee as percentage of loan amount"
    )
    late_payment_penalty = models.DecimalField(
        max_digits=5, 
        decimal_places=2,
        default=5.0,
        help_text="Daily penalty rate for late payments"
    )
    
    # Terms
    duration_months = models.PositiveIntegerField(help_text="Duration in months", default=1)
    min_duration = models.PositiveIntegerField(help_text="Minimum duration in days")
    max_duration = models.PositiveIntegerField(help_text="Maximum duration in days")
    
    # Repayment methods
    available_repayment_methods = models.JSONField(
        default=list,
        help_text="Available repayment methods for this product"
    )
    
    # Requirements
    requires_guarantor = models.BooleanField(default=False)
    requires_collateral = models.BooleanField(default=False)
    minimum_income = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    
    # Rollover settings
    rollover_fee_percentage = models.DecimalField(
        max_digits=5,
        decimal_places=2,
        default=5.0,
        help_text="Rollover fee as percentage of outstanding amount"
    )
    max_rollover_count = models.PositiveIntegerField(
        default=3,
        help_text="Maximum number of times a loan can be rolled over"
    )
    max_rollover_days = models.PositiveIntegerField(
        default=30,
        help_text="Maximum number of days for each rollover"
    )
    
    # Status
    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'loan_products'
        ordering = ['name']
    
    def __str__(self):
        return f"{self.name} ({self.get_product_type_display()})"
    
    def get_interest_rate(self):
        """Get interest rate from settings or fallback to model value"""
        from utils.models import SystemSetting
        setting_key = f"{self.product_type}_interest_rate"
        return SystemSetting.get_float(setting_key, self.interest_rate)
    
    def get_processing_fee(self):
        """Get processing fee from settings or fallback to model value"""
        from utils.models import SystemSetting
        setting_key = f"{self.product_type}_processing_fee"
        return SystemSetting.get_float(setting_key, self.processing_fee)
    
    def get_min_amount(self):
        """Get minimum amount from settings or fallback to model value"""
        from utils.models import SystemSetting
        setting_key = f"{self.product_type}_min_amount"
        return SystemSetting.get_float(setting_key, float(self.min_amount))
    
    def get_max_amount(self):
        """Get maximum amount from settings or fallback to model value"""
        from utils.models import SystemSetting
        setting_key = f"{self.product_type}_max_amount"
        return SystemSetting.get_float(setting_key, float(self.max_amount))
    
    def get_late_payment_penalty(self):
        """Get late payment penalty from settings or fallback to model value"""
        from utils.models import SystemSetting
        return SystemSetting.get_float('late_payment_penalty', self.late_payment_penalty)
    
    def calculate_interest(self, amount, months):
        """Calculate interest for given amount and duration in months"""
        monthly_rate = Decimal(str(self.get_interest_rate())) / Decimal('100')
        return Decimal(str(amount)) * monthly_rate * Decimal(str(months))
    
    def calculate_processing_fee(self, amount):
        """Calculate processing fee for given amount"""
        return Decimal(str(amount)) * (Decimal(str(self.get_processing_fee())) / Decimal('100'))
    
    def get_repayment_methods_display(self):
        """Get human-readable repayment methods"""
        if not self.available_repayment_methods:
            return "Monthly"
        
        method_names = []
        for method in self.available_repayment_methods:
            method_names.append(dict(self.REPAYMENT_METHODS).get(method, method))
        return ", ".join(method_names)
    
    def is_boost_product(self):
        """Check if this is a Boost product"""
        return self.product_type == 'boost'
    
    def is_mwamba_product(self):
        """Check if this is a Mwamba product"""
        return self.product_type == 'mwamba'
    
    def is_imara_product(self):
        """Check if this is a Imara product"""
        return self.product_type == 'imara'
    
    def validate_amount(self, amount):
        """Validate if amount is within product limits"""
        min_amount = Decimal(str(self.get_min_amount()))
        max_amount = Decimal(str(self.get_max_amount()))
        amount = Decimal(str(amount))
        
        if amount < min_amount:
            raise ValueError(f"Amount must be at least KES {min_amount:,.2f}")
        if amount > max_amount:
            raise ValueError(f"Amount cannot exceed KES {max_amount:,.2f}")
        return True
    
    def validate_duration(self, duration_days):
        """Validate if duration is within product limits"""
        if duration_days < self.min_duration:
            raise ValueError(f"Duration must be at least {self.min_duration} days")
        if duration_days > self.max_duration:
            raise ValueError(f"Duration cannot exceed {self.max_duration} days")
        return True
    
    def validate_repayment_method(self, method):
        """Validate if repayment method is available for this product"""
        if method not in self.available_repayment_methods:
            raise ValueError(f"Repayment method {method} is not available for this product")
        return True

    def to_dict(self):
        """Get product details with current settings values"""
        return {
            'id': str(self.id),
            'name': self.name,
            'product_type': self.product_type,
            'description': self.description,
            'min_amount': float(self.get_min_amount()),
            'max_amount': float(self.get_max_amount()),
            'interest_rate': float(self.get_interest_rate()),
            'processing_fee': float(self.get_processing_fee()),
            'late_payment_penalty': float(self.get_late_payment_penalty()),
            'duration_months': self.duration_months,
            'min_duration': self.min_duration,
            'max_duration': self.max_duration,
            'available_repayment_methods': self.available_repayment_methods,
            'requires_guarantor': self.requires_guarantor,
            'requires_collateral': self.requires_collateral,
            'is_active': self.is_active
        }


class LoanApplication(models.Model):
    """
    Loan application model with approval workflow
    """
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('under_review', 'Under Review'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected'),
        ('disbursed', 'Disbursed'),
        ('cancelled', 'Cancelled'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    application_number = models.CharField(max_length=20, unique=True)
    
    # Applicant
    borrower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='loan_applications')
    loan_product = models.ForeignKey(LoanProduct, on_delete=models.CASCADE)
    
    # Loan details
    requested_amount = models.DecimalField(max_digits=12, decimal_places=2)
    requested_duration = models.PositiveIntegerField(help_text="Duration in days")
    purpose = models.TextField()
    repayment_method = models.CharField(max_length=20, choices=[('daily', 'Daily'), ('weekly', 'Weekly'), ('monthly', 'Monthly')], default='monthly')
    
    # Calculated fields
    interest_amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    processing_fee_amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    total_amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    
    # Status and workflow
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    submitted_at = models.DateTimeField(auto_now_add=True)
    reviewed_by = models.ForeignKey(
        User, 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='reviewed_applications'
    )
    reviewed_at = models.DateTimeField(blank=True, null=True)
    approval_notes = models.TextField(blank=True, null=True)
    
    # Documents
    supporting_documents = models.FileField(upload_to='loan_documents/', blank=True, null=True)
    
    # Auto-approval
    auto_approved = models.BooleanField(default=False)
    credit_score = models.PositiveIntegerField(blank=True, null=True)
    
    class Meta:
        db_table = 'loan_applications'
        ordering = ['-submitted_at']
    
    def __str__(self):
        return f"Application {self.application_number} - {self.borrower.get_full_name()}"
    
    def save(self, *args, **kwargs):
        if not self.application_number:
            # Generate application number
            last_app = LoanApplication.objects.order_by('-submitted_at').first()
            if last_app and last_app.application_number:
                try:
                    last_num = int(last_app.application_number.split('-')[1])
                    self.application_number = f"APP-{last_num + 1:06d}"
                except (IndexError, ValueError):
                    # If there's an error parsing the last number, generate a timestamp-based number
                    self.application_number = f"APP-{int(timezone.now().timestamp()):06d}"
            else:
                self.application_number = "APP-000001"
        
        # Calculate amounts if not set
        if not self.interest_amount:
            self.interest_amount = self.loan_product.calculate_interest(
                self.requested_amount, 
                self.requested_duration
            )
        
        if not self.processing_fee_amount:
            self.processing_fee_amount = self.loan_product.calculate_processing_fee(
                self.requested_amount
            )
        
        if not self.total_amount:
            self.total_amount = self.requested_amount + self.interest_amount + self.processing_fee_amount
        
        super().save(*args, **kwargs)
    
    def approve(self, approved_by, notes=""):
        """Approve a loan application and create a loan record"""
        self.status = 'approved'
        self.reviewed_by = approved_by
        self.reviewed_at = timezone.now()
        self.approval_notes = notes
        self.save()
        
        # Create loan record
        loan = Loan.objects.create(
            application=self,
            borrower=self.borrower,
            principal_amount=self.requested_amount,
            interest_amount=self.interest_amount,
            processing_fee=self.processing_fee_amount,
            total_amount=self.total_amount,
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=self.requested_duration),
            duration_days=self.requested_duration,
            status='active'  # Set initial status as active
        )
        
        # Generate a unique loan number
        loan.loan_number = f"LOAN-{str(loan.id).split('-')[0][:6].upper()}"
        loan.save()
        
        return loan
    
    def reject(self, rejected_by, notes=""):
        self.status = 'rejected'
        self.reviewed_by = rejected_by
        self.reviewed_at = timezone.now()
        self.approval_notes = notes
        self.save()

    def calculate_risk_score(self):
        """Calculate a risk score from 0-100 based on various factors"""
        score = 0
        max_score = 0
        
        # Credit Score (max 30 points)
        if self.credit_score:
            max_score += 30
            if self.credit_score >= 700:
                score += 30
            elif self.credit_score >= 600:
                score += 20
            elif self.credit_score >= 500:
                score += 10
        
        # Payment History (max 30 points)
        total_loans = self.borrower.loans.count()
        if total_loans > 0:
            max_score += 30
            paid_loans = self.borrower.loans.filter(status='paid').count()
            payment_ratio = paid_loans / total_loans
            if payment_ratio == 1:
                score += 30
            elif payment_ratio >= 0.7:
                score += 20
            elif payment_ratio >= 0.5:
                score += 10
        
        # Debt-to-Income Ratio (max 20 points)
        if self.borrower.monthly_income:
            max_score += 20
            dti_ratio = (self.total_amount / self.borrower.monthly_income) * 100
            if dti_ratio <= 40:
                score += 20
            elif dti_ratio <= 50:
                score += 10
        
        # Loan Amount vs Income (max 10 points)
        if self.borrower.monthly_income:
            max_score += 10
            amount_ratio = self.requested_amount / self.borrower.monthly_income
            if amount_ratio <= 2:
                score += 10
            elif amount_ratio <= 3:
                score += 5
        
        # Previous Defaults (max 10 points)
        max_score += 10
        if not self.borrower.loans.filter(status='defaulted').exists():
            score += 10
        
        # Return normalized score
        return (score / max_score * 100) if max_score > 0 else 0

    def get_risk_assessment(self):
        """Get a comprehensive risk assessment"""
        score = self.calculate_risk_score()
        
        assessment = {
            'score': score,
            'level': 'low' if score >= 80 else 'medium' if score >= 60 else 'high',
            'factors': []
        }
        
        # Credit Score
        if self.credit_score:
            assessment['factors'].append({
                'name': 'Credit Score',
                'value': self.credit_score,
                'status': 'good' if self.credit_score >= 700 else 'fair' if self.credit_score >= 600 else 'poor'
            })
        
        # Payment History
        total_loans = self.borrower.loans.count()
        if total_loans > 0:
            paid_loans = self.borrower.loans.filter(status='paid').count()
            payment_ratio = paid_loans / total_loans
            assessment['factors'].append({
                'name': 'Payment History',
                'value': f"{paid_loans}/{total_loans} loans paid",
                'status': 'good' if payment_ratio == 1 else 'fair' if payment_ratio >= 0.7 else 'poor'
            })
        
        # Debt-to-Income Ratio
        if self.borrower.monthly_income:
            dti_ratio = (self.total_amount / self.borrower.monthly_income) * 100
            assessment['factors'].append({
                'name': 'Debt-to-Income Ratio',
                'value': f"{dti_ratio:.1f}%",
                'status': 'good' if dti_ratio <= 40 else 'fair' if dti_ratio <= 50 else 'poor'
            })
        
        # Previous Defaults
        defaults = self.borrower.loans.filter(status='defaulted').count()
        if defaults > 0:
            assessment['factors'].append({
                'name': 'Previous Defaults',
                'value': defaults,
                'status': 'poor'
            })
        
        return assessment


class Loan(models.Model):
    """
    Active loan after approval and disbursement
    """
    STATUS_CHOICES = [
        ('active', 'Active'),
        ('paid', 'Paid'),
        ('defaulted', 'Defaulted'),
        ('rolled_over', 'Rolled Over'),
        ('written_off', 'Written Off'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    loan_number = models.CharField(max_length=20, unique=True)
    
    # Application reference
    application = models.OneToOneField(LoanApplication, on_delete=models.CASCADE)
    borrower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='loans')
    
    # Loan details
    principal_amount = models.DecimalField(max_digits=12, decimal_places=2)
    interest_amount = models.DecimalField(max_digits=12, decimal_places=2)
    processing_fee = models.DecimalField(max_digits=12, decimal_places=2)
    total_amount = models.DecimalField(max_digits=12, decimal_places=2)
    
    # Terms
    disbursement_date = models.DateTimeField()
    due_date = models.DateTimeField()
    duration_days = models.PositiveIntegerField()
    
    # Status
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
    
    # Repayment tracking
    amount_paid = models.DecimalField(max_digits=12, decimal_places=2, default=0)
    last_payment_date = models.DateTimeField(blank=True, null=True)
    
    # Rollover
    is_rolled_over = models.BooleanField(default=False)
    original_loan = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
    
    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'loans'
        ordering = ['-created_at']
    
    def __str__(self):
        return f"Loan {self.loan_number} - {self.borrower.get_full_name()}"
    
    def save(self, *args, **kwargs):
        if not self.loan_number:
            # Generate loan number
            last_loan = Loan.objects.order_by('-id').first()
            if last_loan and last_loan.loan_number:
                try:
                    # Try to extract the number from the last loan number
                    last_num = int(''.join(filter(str.isdigit, last_loan.loan_number)))
                    next_num = last_num + 1
                except (ValueError, TypeError):
                    # If there's any error parsing the number, start from 1
                    next_num = 1
            else:
                next_num = 1
            
            self.loan_number = f"LOAN-{next_num:06d}"
        super().save(*args, **kwargs)
    
    @property
    def outstanding_amount(self):
        return self.total_amount - self.amount_paid
    
    @property
    def is_overdue(self):
        return timezone.now() > self.due_date and self.outstanding_amount > 0
    
    @property
    def days_overdue(self):
        if self.is_overdue:
            return (timezone.now() - self.due_date).days
        return 0
    
    def calculate_penalty(self):
        """Calculate penalty for overdue payments"""
        if self.is_overdue:
            daily_penalty_rate = self.application.loan_product.late_payment_penalty / 100
            return self.outstanding_amount * daily_penalty_rate * self.days_overdue
        return Decimal('0.00')


class Repayment(models.Model):
    """
    Loan repayment tracking
    """
    PAYMENT_METHODS = [
        ('mpesa', 'M-Pesa'),
        ('bank', 'Bank Transfer'),
        ('cash', 'Cash'),
        ('cheque', 'Cheque'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    loan = models.ForeignKey(Loan, on_delete=models.CASCADE, related_name='repayments')
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    payment_method = models.CharField(max_length=20, choices=PAYMENT_METHODS)
    
    # M-Pesa details
    mpesa_transaction_id = models.CharField(max_length=50, blank=True, null=True)
    mpesa_phone_number = models.CharField(max_length=17, blank=True, null=True)
    
    # Receipt
    receipt_number = models.CharField(max_length=20, unique=True)
    
    # Timestamps
    payment_date = models.DateTimeField(auto_now_add=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'repayments'
        ordering = ['-payment_date']
    
    def __str__(self):
        return f"Repayment {self.receipt_number} - {self.loan.loan_number}"
    
    def save(self, *args, **kwargs):
        if not self.receipt_number:
            # Generate receipt number
            last_receipt = Repayment.objects.order_by('-id').first()
            if last_receipt:
                last_num = int(last_receipt.receipt_number.split('-')[1])
                self.receipt_number = f"RCP-{last_num + 1:06d}"
            else:
                self.receipt_number = "RCP-000001"
        
        super().save(*args, **kwargs)
        
        # Update loan payment tracking
        self.loan.amount_paid += self.amount
        self.loan.last_payment_date = self.payment_date
        if self.loan.amount_paid >= self.loan.total_amount:
            self.loan.status = 'paid'
        self.loan.save()


class RolloverRequest(models.Model):
    """
    Loan rollover requests
    """
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('approved', 'Approved'),
        ('rejected', 'Rejected'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    loan = models.ForeignKey(Loan, on_delete=models.CASCADE, related_name='rollover_requests')
    borrower = models.ForeignKey(User, on_delete=models.CASCADE)
    
    # Rollover details
    requested_duration = models.PositiveIntegerField(help_text="Additional days")
    reason = models.TextField()
    rollover_fee = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    
    # Status
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    reviewed_by = models.ForeignKey(
        User, 
        on_delete=models.SET_NULL, 
        null=True, 
        blank=True, 
        related_name='reviewed_rollovers'
    )
    reviewed_at = models.DateTimeField(blank=True, null=True)
    review_notes = models.TextField(blank=True, null=True)
    
    # Timestamps
    requested_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        db_table = 'rollover_requests'
        ordering = ['-requested_at']
    
    def __str__(self):
        return f"Rollover for {self.loan.loan_number} - {self.status}"
    
    def approve(self, approved_by, notes=""):
        self.status = 'approved'
        self.reviewed_by = approved_by
        self.reviewed_at = timezone.now()
        self.review_notes = notes
        self.save()
        
        # Create new loan with extended terms
        new_loan = Loan.objects.create(
            application=self.loan.application,
            borrower=self.loan.borrower,
            principal_amount=self.loan.outstanding_amount,
            interest_amount=self.loan.application.loan_product.calculate_interest(
                self.loan.outstanding_amount, 
                self.requested_duration
            ),
            processing_fee=self.rollover_fee or Decimal('0.00'),
            total_amount=self.loan.outstanding_amount + self.rollover_fee,
            disbursement_date=timezone.now(),
            due_date=timezone.now() + timezone.timedelta(days=self.requested_duration),
            duration_days=self.requested_duration,
            original_loan=self.loan,
        )
        
        # Mark original loan as rolled over
        self.loan.status = 'rolled_over'
        self.loan.is_rolled_over = True
        self.loan.save()
        
        return new_loan
    
    def reject(self, rejected_by, notes=""):
        self.status = 'rejected'
        self.reviewed_by = rejected_by
        self.reviewed_at = timezone.now()
        self.review_notes = notes
        self.save()



class MpesaTransaction(models.Model):
    """
    M-Pesa transaction logs
    """
    TRANSACTION_TYPES = [
        ('c2b', 'Customer to Business'),
        ('b2c', 'Business to Customer'),
        ('stk_push', 'STK Push'),
    ]
    
    STATUS_CHOICES = [
        ('pending', 'Pending'),
        ('success', 'Success'),
        ('failed', 'Failed'),
        ('cancelled', 'Cancelled'),
    ]
    
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    transaction_type = models.CharField(max_length=20, choices=TRANSACTION_TYPES)
    amount = models.DecimalField(max_digits=12, decimal_places=2)
    phone_number = models.CharField(max_length=17)
    
    # M-Pesa response
    mpesa_transaction_id = models.CharField(max_length=50, blank=True, null=True)
    merchant_request_id = models.CharField(max_length=50, blank=True, null=True)
    checkout_request_id = models.CharField(max_length=50, blank=True, null=True)
    
    # Status
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
    result_code = models.CharField(max_length=10, blank=True, null=True)
    result_description = models.TextField(blank=True, null=True)
    
    # Related loan/repayment
    loan = models.ForeignKey(Loan, on_delete=models.SET_NULL, null=True, blank=True)
    repayment = models.ForeignKey(Repayment, on_delete=models.SET_NULL, null=True, blank=True)
    
    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    
    class Meta:
        db_table = 'mpesa_transactions'
        ordering = ['-created_at']
    
    def __str__(self):
        return f"M-Pesa {self.transaction_type} - {self.phone_number} - {self.amount}"
