# content/billing.py
from __future__ import annotations
from dataclasses import dataclass
from decimal import Decimal
from datetime import date

from django.conf import settings
from django.db import transaction
from django.utils import timezone
from django.db.models import Q

# Import models safely
from .models import (
    TuitionInvoice, StudentProfile, TuitionPayment, AdmissionApplication,
    _ensure_custom_invoice
)

# ----------------------------
# Internal month helpers
# ----------------------------
def _ym_today() -> tuple[int, int]:
    """Return (year, month) for local date."""
    d = timezone.localdate()
    return d.year, d.month

def _next_ym(year: int, month: int) -> tuple[int, int]:
    """Return (year, month) for the next month."""
    return (year + 1, 1) if month == 12 else (year, month + 1)

# ----------------------------
# Fee resolution
# ----------------------------
def monthly_fee_for_user(user) -> Decimal:
    prof = StudentProfile.objects.filter(user=user).select_related("school_class").first()
    if prof and prof.monthly_fee and prof.monthly_fee > 0:
        return Decimal(prof.monthly_fee)
    if prof and prof.school_class and prof.school_class.monthly_fee and prof.school_class.monthly_fee > 0:
        return Decimal(prof.school_class.monthly_fee)
    return Decimal(getattr(settings, "DEFAULT_MONTHLY_FEE", 2000))

# ----------------------------
# Invoice ensuring / creation
# ----------------------------
@transaction.atomic
def get_or_create_month_invoice(user, year: int, month: int) -> TuitionInvoice | None:
    """
    Create or fetch a monthly invoice for (user, year, month).
    SECURED: Checks approval status via models.py logic.
    """
    # 1. Security Check
    if not (user.is_staff or user.is_superuser):
        match_q = Q()
        if user.email: match_q |= Q(email__iexact=user.email)
        if user.username: match_q |= Q(phone__iexact=user.username)
        
        is_approved = False
        if match_q:
            is_approved = AdmissionApplication.objects.filter(
                match_q, payment_status__in=['paid', 'approved']
            ).exists()
        
        if not is_approved:
            return TuitionInvoice.objects.filter(
                student=user, kind="monthly", period_year=year, period_month=month
            ).first()

    # 2. Calculate correct Due Date (Target Month, not Today)
    from datetime import date
    try:
        # Try 28th of the target month
        target_due_date = date(year, month, 28)
    except ValueError:
        # Fallback for Feb (25th)
        target_due_date = date(year, month, 25)

    # 3. Create/Get Invoice
    inv, created = TuitionInvoice.objects.select_for_update().get_or_create(
        student=user,
        kind="monthly",
        period_year=year,
        period_month=month,
        defaults={
            "tuition_amount": monthly_fee_for_user(user),
            "paid_amount": Decimal("0.00"),
            "due_date": target_due_date,  # ✅ FIXED: Uses the calculated future date
        },
    )

    # 4. Update amount if needed
    expected = monthly_fee_for_user(user)
    if not created and inv.paid_amount == 0 and inv.tuition_amount != expected:
        inv.tuition_amount = expected
        inv.save(update_fields=["tuition_amount"])
    
    return inv


def _ensure_addons_for_month(user, year, month):
    """
    Generates separate invoices for Bus (Monthly), Hostel (Monthly),
    and Exam (Quarterly: March, June, Sept).
    """
    try:
        prof = user.student_profile
    except StudentProfile.DoesNotExist:
        return

    # FIX: Find application using Email or Phone (fixing the user=user crash)
    match_q = Q()
    if user.email: match_q |= Q(email__iexact=user.email)
    if user.username: match_q |= Q(phone__iexact=user.username)
    
    app = None
    if match_q:
        app = AdmissionApplication.objects.filter(match_q, payment_status__in=['paid', 'approved']).first()

    # Date Helpers
    month_date = date(year, month, 1)
    date_str = month_date.strftime('%b %Y')
    try: due_dt = month_date.replace(day=28)
    except ValueError: due_dt = month_date.replace(day=25)

    # 1. BUS FEE
    if prof.has_bus_service and prof.bus_monthly_fee > 0:
        title = f"Bus Service ({date_str})"
        if not TuitionInvoice.objects.filter(student=user, title=title).exists():
            _ensure_custom_invoice(user, title, prof.bus_monthly_fee, due_date=due_dt)

    # 2. HOSTEL FEE
    if prof.has_hostel_seat and prof.hostel_monthly_fee > 0:
        title = f"Hostel Fee ({date_str})"
        if not TuitionInvoice.objects.filter(student=user, title=title).exists():
            _ensure_custom_invoice(user, title, prof.hostel_monthly_fee, due_date=due_dt)

    # 3. EXAM FEE (March, June, Sept)
    if month in [3, 6, 9]:
        # If app found, check setting. If app NOT found, default to True/500 to be safe
        should_charge = getattr(app, 'add_exam', True) if app else True
        amount = getattr(app, 'fee_exam', 500) if app else 500

        if should_charge and amount > 0:
            title = f"Exam Fee ({date_str})"
            if not TuitionInvoice.objects.filter(student=user, title=title).exists():
                _ensure_custom_invoice(user, title, amount, due_date=due_dt)


@transaction.atomic
def ensure_monthly_window_for_user(user, months_ahead: int = 1) -> tuple[TuitionInvoice, TuitionInvoice | None]:
    """
    Ensure the current month's invoice exists + Addons.
    """
    y, m = _ym_today()
    
    # 1. Current Month
    current = get_or_create_month_invoice(user, y, m)
    if current:
        _ensure_addons_for_month(user, y, m)

    # 2. Future Months
    last = None
    ay, am = y, m
    for _ in range(months_ahead):
        ay, am = _next_ym(ay, am)
        last = get_or_create_month_invoice(user, ay, am)
        if last:
            _ensure_addons_for_month(user, ay, am)

    return current, last


def ensure_current_month_invoice(user) -> TuitionInvoice:
    y, m = _ym_today()
    return get_or_create_month_invoice(user, y, m)


# ----------------------------
# Dues summary (for UI)
# ----------------------------
@dataclass
class DuesSummary:
    total_due: Decimal
    unpaid_count: int
    unpaid: list
    upcoming: TuitionInvoice | None

def compute_dues_summary(user) -> DuesSummary:
    # ✅ Auto-generate logic
    try:
        current, next_inv = ensure_monthly_window_for_user(user, months_ahead=1)
        upcoming = next_inv
    except Exception:
        upcoming = None

    qs = (
        TuitionInvoice.objects
        .filter(student=user)
        .order_by("period_year", "period_month", "id")
    )

    unpaid = [i for i in qs if (i.paid_amount or 0) < (i.tuition_amount or 0)]
    total_due = sum(
        ((i.tuition_amount or Decimal("0")) - (i.paid_amount or Decimal("0")))
        for i in unpaid
    )

    return DuesSummary(
        total_due=total_due,
        unpaid_count=len(unpaid),
        unpaid=unpaid,
        upcoming=upcoming,
    )

# ----------------------------
# Bulk allocation (pay all dues)
# ----------------------------
@transaction.atomic
def allocate_payment_across_invoices(user, amount: Decimal, *, provider: str, txn_id: str | None = None):
    if amount is None or Decimal(amount) <= 0:
        return []

    invoices = (
        TuitionInvoice.objects
        .select_for_update()
        .filter(student=user)
        .order_by("period_year", "period_month", "id")
    )

    remaining = Decimal(amount)
    created = []

    for inv in invoices:
        bal = (inv.tuition_amount or Decimal("0")) - (inv.paid_amount or Decimal("0"))
        if bal <= 0: continue
        if remaining <= 0: break

        pay_now = min(bal, remaining)
        tp = TuitionPayment.objects.create(
            invoice=inv, amount=pay_now, provider=provider, txn_id=txn_id, paid_on=timezone.localdate(),
        )
        created.append(tp)
        inv.paid_amount = (inv.paid_amount or Decimal("0")) + pay_now
        inv.save(update_fields=["paid_amount"])
        remaining -= pay_now

    return created

def create_custom_invoice(user, *, title: str, amount, due_date=None):
    from .models import _ensure_custom_invoice
    return _ensure_custom_invoice(user, title, amount, due_date)