from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator, MaxValueValidator
from django.db import models
from django.utils import timezone

from academics.models import AcademicTerm, StudentEnrollment, AcademicClass
from people.models import Student


class FeeCategory(models.Model):
    """
    Simple fee categorization
    """

    name = models.CharField(max_length=100)
    description = models.TextField(blank=True, null=True)
    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name_plural = "Fee Categories"
        ordering = ["name"]

    def __str__(self):
        return self.name


class FeeComponent(models.Model):
    """
    Individual fee items that can be charged
    """

    CALCULATION_TYPES = (
        ("fixed", "Fixed Amount"),
        ("per_term", "Per Term"),
        ("monthly", "Monthly"),
        ("yearly", "Yearly"),
    )

    name = models.CharField(max_length=100)
    code = models.CharField(max_length=30, unique=True)
    category = models.ForeignKey(
        FeeCategory, on_delete=models.SET_NULL, null=True, related_name="fee_components"
    )
    description = models.TextField(blank=True, null=True)

    calculation_type = models.CharField(
        max_length=20, choices=CALCULATION_TYPES, default="fixed"
    )
    is_mandatory = models.BooleanField(default=True)
    is_recurring = models.BooleanField(default=True)

    # Late fee rules (simple)
    late_fee_applicable = models.BooleanField(default=False)
    late_fee_type = models.CharField(
        max_length=20,
        choices=(
            ("fixed", "Fixed Amount"),
            ("percentage", "Percentage of Fee"),
        ),
        null=True,
        blank=True,
    )
    late_fee_value = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0)],
        null=True,
        blank=True,
    )
    grace_days = models.IntegerField(default=0, validators=[MinValueValidator(0)])

    is_active = models.BooleanField(default=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["category", "name"]

    def __str__(self):
        return f"{self.code} - {self.name}"


class ClassFeeStructure(models.Model):
    """
    Fee structure assigned to a specific class and term
    """

    academic_class = models.ForeignKey(
        AcademicClass, on_delete=models.CASCADE, related_name="fee_structures"
    )
    fee_component = models.ForeignKey(
        FeeComponent, on_delete=models.CASCADE, related_name="class_fee_structures"
    )
    academic_term = models.ForeignKey(
        AcademicTerm, on_delete=models.CASCADE, related_name="fee_structures"
    )

    amount = models.DecimalField(
        max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]
    )

    due_date = models.DateField()
    late_fee_applicable = models.BooleanField(default=False)

    notes = models.TextField(blank=True, null=True)
    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ["academic_class", "fee_component", "academic_term"]
        ordering = ["academic_class", "fee_component"]

    def clean(self):
        if (
            self.academic_term
            and self.academic_class.academic_year_id
            != self.academic_term.academic_year_id
        ):
            raise ValidationError(
                "Term must belong to the same academic year as the class"
            )

    def __str__(self):
        return (
            f"{self.academic_class} - {self.fee_component.name} ({self.academic_term})"
        )


class StudentFeeAssignment(models.Model):
    """
    Student-specific fee assignments (for discounts/concessions)
    """

    DISCOUNT_TYPES = (
        ("percentage", "Percentage Discount"),
        ("amount", "Fixed Amount Discount"),
        ("waiver", "Full Waiver"),
    )

    student = models.ForeignKey(
        Student, on_delete=models.CASCADE, related_name="fee_assignments"
    )
    enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="fee_assignments"
    )
    class_fee_structure = models.ForeignKey(
        ClassFeeStructure, on_delete=models.CASCADE, related_name="student_assignments"
    )

    # Discount/Concession
    discount_type = models.CharField(
        max_length=20, choices=DISCOUNT_TYPES, null=True, blank=True
    )
    discount_value = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0)],
        null=True,
        blank=True,
        help_text="Percentage or amount based on discount_type",
    )

    # Override due date if needed
    due_date_override = models.DateField(null=True, blank=True)

    reason = models.TextField(blank=True, null=True)
    is_active = models.BooleanField(default=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        unique_together = ["enrollment", "class_fee_structure"]
        ordering = ["student", "class_fee_structure"]

    def clean(self):
        if (
            self.enrollment.academic_class_id
            != self.class_fee_structure.academic_class_id
        ):
            raise ValidationError("Fee structure must match student's enrolled class")

    def calculate_final_amount(self):
        """Calculate fee after discount"""
        base_amount = self.class_fee_structure.amount

        if not self.discount_type:
            return base_amount

        if self.discount_type == "percentage" and self.discount_value:
            return base_amount * (1 - self.discount_value / 100)
        elif self.discount_type == "amount" and self.discount_value:
            return max(0, base_amount - self.discount_value)
        elif self.discount_type == "waiver":
            return 0

        return base_amount

    def __str__(self):
        return f"{self.student} - {self.class_fee_structure}"

class FeePayment(models.Model):
    """
    Tracks individual fee payments made by students
    Updated to support partial payments with fee component tracking
    """

    PAYMENT_METHODS = (
        ("cash", "Cash"),
        ("bank_transfer", "Bank Transfer"),
        ("cheque", "Cheque"),
        ("credit_card", "Credit Card"),
        ("debit_card", "Debit Card"),
        ("mobile_money", "Mobile Money"),
        ("online", "Online Payment"),
        ("other", "Other"),
    )

    PAYMENT_STATUS = (
        ("pending", "Pending"),
        ("completed", "Completed"),
        ("failed", "Failed"),
        ("refunded", "Refunded"),
        ("cancelled", "Cancelled"),
        ("bounced", "Bounced"),
    )

    # NEW: Payment type to track if it's full, partial, or component-wise
    PAYMENT_TYPES = (
        ("full", "Full Payment"),
        ("partial", "Partial Payment"),
        ("component", "Component-wise Payment"),
    )

    # Core relationships
    student_enrollment = models.ForeignKey(
        StudentEnrollment, on_delete=models.CASCADE, related_name="fee_payments"
    )
    student = models.ForeignKey(
        Student, on_delete=models.CASCADE, related_name="fee_payments"
    )

    # Payment details
    receipt_number = models.CharField(max_length=50, unique=True)
    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]
    )
    payment_date = models.DateField(default=timezone.now)
    payment_method = models.CharField(
        max_length=20, choices=PAYMENT_METHODS, default="cash"
    )
    status = models.CharField(max_length=20, choices=PAYMENT_STATUS, default="pending")
    
    # NEW: Payment type field with default 'full' for existing records
    payment_type = models.CharField(
        max_length=20, 
        choices=PAYMENT_TYPES, 
        default="full",
        help_text="Type of payment - full, partial, or component-wise"
    )

    # Reference information
    transaction_reference = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        help_text="External transaction ID/reference",
    )
    cheque_number = models.CharField(
        max_length=50,
        blank=True,
        null=True,
        help_text="Cheque number if payment method is cheque",
    )
    bank_name = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        help_text="Bank name for cheque/bank transfer",
    )

    # Fee allocation (optional - if paying specific fee components)
    fee_assignments = models.ManyToManyField(
        StudentFeeAssignment,
        blank=True,
        related_name="payments",
        help_text="Specific fee assignments this payment covers",
    )

    # NEW: Direct ManyToMany to FeeComponent through PaymentFeeComponent
    fee_components = models.ManyToManyField(
        FeeComponent,
        through='PaymentFeeComponent',
        related_name='payments',
        blank=True,
        help_text="Fee components covered by this payment"
    )

    # Payment breakdown
    principal_amount = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0)],
        help_text="Amount towards actual fees",
    )
    late_fee_amount = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        default=0,
        validators=[MinValueValidator(0)],
        help_text="Late payment penalty charged",
    )

    # Balance tracking
    balance_before = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0)],
        help_text="Balance before this payment",
    )
    balance_after = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        validators=[MinValueValidator(0)],
        help_text="Balance after this payment",
    )

    # Bounce charge details (for bounced cheques)
    is_bounced = models.BooleanField(default=False)
    bounce_date = models.DateField(null=True, blank=True)
    bounce_reason = models.TextField(blank=True, null=True)
    bounce_charge_amount = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        default=0,
        validators=[MinValueValidator(0)],
        help_text="Bank charges for bounced cheque",
    )
    bounce_charge_paid = models.BooleanField(default=False)
    bounce_charge_paid_date = models.DateField(null=True, blank=True)

    # EFT (Electronic Fund Transfer) details
    eft_reference = models.CharField(
        max_length=100, blank=True, null=True, help_text="EFT reference number"
    )
    eft_initiation_date = models.DateField(null=True, blank=True)
    eft_settlement_date = models.DateField(null=True, blank=True)
    eft_bank_name = models.CharField(
        max_length=100, blank=True, null=True, help_text="Bank name for EFT"
    )
    eft_account_number = models.CharField(
        max_length=50,
        blank=True,
        null=True,
        help_text="Last 4 digits of account number for reference",
    )

    # Notes and metadata - REMOVED recorded_by ForeignKey to auth.User
    notes = models.TextField(blank=True, null=True)

    # You can add a CharField for recording who made the payment if needed
    recorded_by_name = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        help_text="Name of person who recorded the payment",
    )

    # Timestamps
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-payment_date", "-created_at"]
        indexes = [
            models.Index(fields=["receipt_number"]),
            models.Index(fields=["student_enrollment", "status"]),
            models.Index(fields=["payment_date"]),
            models.Index(fields=["is_bounced"]),
            models.Index(fields=["payment_type"]),  # NEW index for payment_type
        ]

    def __str__(self):
        return f"{self.receipt_number} - {self.student} - {self.amount_paid}"

    def save(self, *args, **kwargs):
        if not self.receipt_number:
            self.receipt_number = self.generate_receipt_number()
        super().save(*args, **kwargs)

    def generate_receipt_number(self):
        """Generate a unique receipt number"""
        year = timezone.now().strftime("%Y")
        month = timezone.now().strftime("%m")

        # Get the count of payments for this month
        last_payment = (
            FeePayment.objects.filter(receipt_number__startswith=f"RCP-{year}{month}")
            .order_by("-receipt_number")
            .first()
        )

        if last_payment:
            last_number = int(last_payment.receipt_number.split("-")[-1])
            new_number = last_number + 1
        else:
            new_number = 1

        return f"RCP-{year}{month}-{new_number:06d}"

    def mark_as_bounced(self, reason, bounce_charge=0, bounce_date=None):
        """
        Mark a payment as bounced (e.g., bounced cheque)
        """
        self.status = "bounced"
        self.is_bounced = True
        self.bounce_reason = reason
        self.bounce_charge_amount = bounce_charge
        self.bounce_date = bounce_date or timezone.now().date()

        # Reverse the payment effect on balance
        self.balance_after = self.balance_before

        self.save()

    def update_eft_details(self, reference, settlement_date, bank_name=None):
        """
        Update EFT payment details
        """
        self.eft_reference = reference
        self.eft_settlement_date = settlement_date
        if bank_name:
            self.eft_bank_name = bank_name
        self.save()

    # NEW: Helper methods for component-wise payments
    def get_component_breakdown(self):
        """Get breakdown of payment by fee components"""
        return self.payment_components.select_related('fee_component').all()

    def is_component_paid(self, fee_component_id):
        """Check if a specific fee component is covered by this payment"""
        return self.payment_components.filter(fee_component_id=fee_component_id).exists()

    def get_total_allocated_to_components(self):
        """Get total amount allocated to specific components"""
        total = self.payment_components.aggregate(
            total=models.Sum('amount_paid')
        )['total'] or 0
        return total


# NEW: Through model for FeePayment to FeeComponent relationship
class PaymentFeeComponent(models.Model):
    """
    Through model for FeePayment to FeeComponent relationship
    Tracks how much was paid for each fee component in a payment
    """

    payment = models.ForeignKey(
        FeePayment, 
        on_delete=models.CASCADE, 
        related_name='payment_components'
    )
    fee_component = models.ForeignKey(
        FeeComponent, 
        on_delete=models.CASCADE, 
        related_name='payment_components'
    )
    
    # Amount paid for this specific component
    amount_paid = models.DecimalField(
        max_digits=10, 
        decimal_places=2, 
        validators=[MinValueValidator(0)]
    )
    
    # Optional: Link to the class fee structure if needed
    class_fee_structure = models.ForeignKey(
        ClassFeeStructure,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='payment_components'
    )
    
    # Optional: Link to student fee assignment if applicable
    student_fee_assignment = models.ForeignKey(
        StudentFeeAssignment,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name='payment_components'
    )
    
    # Academic term for this component
    academic_term = models.ForeignKey(
        AcademicTerm,
        on_delete=models.SET_NULL,
        null=True,
        related_name='payment_components'
    )
    
    # Notes specific to this component payment
    notes = models.TextField(blank=True, null=True)
    
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ['payment', 'fee_component', 'academic_term']
        ordering = ['payment', 'fee_component']

    def __str__(self):
        return f"{self.payment.receipt_number} - {self.fee_component.name}: {self.amount_paid}"

    def clean(self):
        """Validate that total component amounts don't exceed payment amount"""
        if self.payment_id:
            total_allocated = self.payment.payment_components.exclude(
                id=self.id
            ).aggregate(total=models.Sum('amount_paid'))['total'] or 0
            
            if total_allocated + self.amount_paid > self.payment.amount_paid:
                raise ValidationError(
                    f"Total allocated amount ({total_allocated + self.amount_paid}) "
                    f"exceeds payment amount ({self.payment.amount_paid})"
                )

    def save(self, *args, **kwargs):
        self.clean()
        super().save(*args, **kwargs)
        
class PaymentAllocation(models.Model):
    """
    Detailed allocation of payments to specific fee components
    This provides more granular tracking than the M2M field above
    """

    payment = models.ForeignKey(
        FeePayment, on_delete=models.CASCADE, related_name="allocations"
    )
    fee_assignment = models.ForeignKey(
        StudentFeeAssignment,
        on_delete=models.CASCADE,
        related_name="payment_allocations",
    )

    amount_allocated = models.DecimalField(
        max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]
    )

    # Track which specific fee component this allocation covers
    academic_term = models.ForeignKey(
        AcademicTerm, on_delete=models.CASCADE, related_name="payment_allocations"
    )

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        unique_together = ["payment", "fee_assignment"]
        ordering = ["payment", "-created_at"]

    def __str__(self):
        return f"{self.payment.receipt_number} - {self.fee_assignment} - {self.amount_allocated}"


class OutstandingBalance(models.Model):
    """
    Tracks current outstanding balance for each enrollment
    This can be updated via signals or background tasks for performance
    """

    student_enrollment = models.OneToOneField(
        StudentEnrollment, on_delete=models.CASCADE, related_name="outstanding_balance"
    )

    total_fees = models.DecimalField(
        max_digits=10, decimal_places=2, default=0, validators=[MinValueValidator(0)]
    )
    total_paid = models.DecimalField(
        max_digits=10, decimal_places=2, default=0, validators=[MinValueValidator(0)]
    )
    balance_due = models.DecimalField(
        max_digits=10, decimal_places=2, default=0, validators=[MinValueValidator(0)]
    )

    # Breakdown of overdue amounts
    overdue_amount = models.DecimalField(
        max_digits=10, decimal_places=2, default=0, validators=[MinValueValidator(0)]
    )
    days_overdue = models.IntegerField(default=0)

    # Late fees accrued
    total_late_fees = models.DecimalField(
        max_digits=10, decimal_places=2, default=0, validators=[MinValueValidator(0)]
    )

    last_calculated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-balance_due"]

    def __str__(self):
        return f"{self.student_enrollment} - Balance: {self.balance_due}"

    def update_balance(self):
        """Recalculate the outstanding balance"""
        # This would aggregate all fee assignments and payments
        # Implementation depends on your business logic
        pass


class TransportFeePayment(models.Model):
    """
    Payment ledger for a student's transport fee (StudentTransport.fee_amount).
    Kept separate from FeePayment/PaymentFeeComponent since transport fee is a
    single flat amount per assignment, not a class-level FeeComponent — this
    lets it be surfaced as its own component in Fee Collections without a fake
    ClassFeeStructure/FeeComponent row.
    """

    student_transport = models.ForeignKey(
        "transport.StudentTransport", on_delete=models.CASCADE, related_name="fee_payments"
    )

    payment = models.ForeignKey(
        FeePayment,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="transport_payments",
        help_text=(
            "FeePayment this was collected under via the parent app, if any. "
            "Null for payments recorded directly via the school office panel."
        ),
    )

    amount_paid = models.DecimalField(
        max_digits=10, decimal_places=2, validators=[MinValueValidator(0)]
    )
    payment_date = models.DateField(default=timezone.now)
    payment_method = models.CharField(max_length=20, default="cash")
    notes = models.TextField(blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return f"Transport payment - {self.student_transport} - {self.amount_paid}"
