"""
academics/report_card_generator.py

Builds the context dictionaries for all 4 report card templates
and generates PDFs using WeasyPrint.

Usage:
    from academics.report_card_generator import generate_report_card_pdf, generate_bulk_pdf
    pdf_bytes = generate_report_card_pdf(report_card)
    pdf_bytes = generate_bulk_pdf(school_class, term)
"""

import io
import logging
from collections import defaultdict
from decimal import Decimal, ROUND_HALF_UP

from django.template.loader import render_to_string
from django.utils import timezone

logger = logging.getLogger(__name__)

# Template map: report_type → template path
TEMPLATE_MAP = {
    'ap': 'report_cards/anglophone_primary.html',
    'an': 'report_cards/anglophone_nursery.html',
    'fp': 'report_cards/francophone_primary.html',
    'fn': 'report_cards/francophone_nursery.html',
}

# Month labels per term
MONTH_LABELS = {
    1: ['1st Month', '2nd Month', '3rd Month'],
    2: ['4th Month', '5th Month', '6th Month'],
    3: ['7th Month', '8th Month', '9th Month'],
}

MONTH_LABELS_FR = {
    1: ['1er Mois', '2e Mois', '3e Mois'],
    2: ['4e Mois', '5e Mois', '6e Mois'],
    3: ['7e Mois', '8e Mois', '9e Mois'],
}


def _get_month_labels(report_type, term_number):
    if report_type in ('fp', 'fn'):
        return MONTH_LABELS_FR[term_number]
    return MONTH_LABELS[term_number]


def _d2(value):
    """Round Decimal to 2 places, or return empty string."""
    if value is None:
        return ''
    return str(Decimal(str(value)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP))


def _build_term_data(student, term, report_type):
    """
    Build the full data dict for one term's table rows.

    Returns a dict with:
        term, month_labels, rows, month_totals, month_averages,
        total_synthese, average, class_size
    """
    from academics.models_grades import (
        ReportSubject, MonthlyMark, TermSynthesis, ReportCard
    )

    subjects = ReportSubject.objects.filter(
        report_type=report_type
    ).order_by('order')

    # Fetch all monthly marks for this student/term/subjects
    month_numbers = term.month_range  # e.g. [1,2,3] for term 1
    marks_qs = MonthlyMark.objects.filter(
        student=student, term=term,
        subject__report_type=report_type
    ).select_related('subject')

    # Build mark lookup: {subject_id: {month_number: score}}
    mark_lookup = defaultdict(dict)
    for m in marks_qs:
        mark_lookup[m.subject_id][m.month_number] = m.score

    # Fetch syntheses
    synth_qs = TermSynthesis.objects.filter(
        student=student, term=term,
        subject__report_type=report_type
    ).select_related('subject')
    synth_lookup = {s.subject_id: s for s in synth_qs}

    # Compute month column totals/averages
    month_score_columns = {mn: [] for mn in month_numbers}
    synthese_scores = []

    # ── Build rows with rowspan tracking ──
    # Group subjects by domain (and activity for nursery)
    domain_groups = defaultdict(list)
    for subj in subjects:
        domain_groups[subj.domain].append(subj)

    rows = []
    for domain, domain_subjects in domain_groups.items():
        # For nursery formats: further group by activity
        activity_groups = defaultdict(list)
        for s in domain_subjects:
            activity_groups[s.activity].append(s)

        domain_rowspan = len(domain_subjects)
        domain_shown = False

        for activity, activity_subjects in activity_groups.items():
            activity_rowspan = len(activity_subjects)
            activity_shown = False

            for subj in activity_subjects:
                month_scores = []
                for mn in month_numbers:
                    score = mark_lookup[subj.id].get(mn)
                    if score is not None:
                        month_score_columns[mn].append(float(score))
                    month_scores.append(_d2(score) if score is not None else '')

                synth = synth_lookup.get(subj.id)
                synthese_val = synth.synthese_score if synth else None
                appreciation = synth.appreciation if synth else ''
                if synthese_val is not None:
                    synthese_scores.append(float(synthese_val))

                row = {
                    'domain': domain,
                    'activity': activity,
                    'sub_activity': subj.sub_activity,
                    'show_domain': not domain_shown,
                    'domain_rowspan': domain_rowspan if not domain_shown else 0,
                    'show_activity': not activity_shown and bool(activity),
                    'activity_rowspan': activity_rowspan if not activity_shown else 0,
                    'month_scores': month_scores,
                    'synthese': _d2(synthese_val) if synthese_val is not None else '',
                    'appreciation': appreciation,
                }
                rows.append(row)
                domain_shown = True
                activity_shown = True

    # Compute column totals / averages
    month_totals = []
    month_averages = []
    for mn in month_numbers:
        col = month_score_columns[mn]
        if col:
            total = sum(col)
            avg = total / len(col)
            month_totals.append(_d2(total))
            month_averages.append(_d2(avg))
        else:
            month_totals.append('')
            month_averages.append('')

    total_synthese = _d2(sum(synthese_scores)) if synthese_scores else ''
    average = _d2(sum(synthese_scores) / len(synthese_scores)) if synthese_scores else ''

    # Class size
    from academics.models import SchoolClass
    try:
        rc = ReportCard.objects.get(student=student, term=term)
        class_size = ReportCard.objects.filter(
            term=term, school_class=rc.school_class
        ).count()
    except Exception:
        class_size = '—'

    return {
        'term': term,
        'month_labels': _get_month_labels(report_type, term.term),
        'rows': rows,
        'month_totals': month_totals,
        'month_averages': month_averages,
        'total_synthese': total_synthese,
        'average': average,
        'class_size': class_size,
    }


def build_report_card_context(report_card):
    """
    Build the full template context for one report card.

    For a single-term request: renders only that term's table.
    For a Term 3 card: also computes annual results.
    """
    from academics.models_grades import AcademicTerm, ReportCard

    student = report_card.student
    rtype   = report_card.report_type

    # Gather all terms for this academic year (could be 1, 2, or all 3)
    current_term = report_card.term
    all_terms = AcademicTerm.objects.filter(
        academic_year=current_term.academic_year
    ).order_by('term')

    # Decide which terms to include in this PDF
    # Show up to and including the current term
    terms_to_show = [t for t in all_terms if t.term <= current_term.term]

    terms_data = [
        _build_term_data(student, t, rtype) for t in terms_to_show
    ]

    # Fetch the school
    from core.models import School
    try:
        school = School.objects.first()
    except Exception:
        school = None

    return {
        'report_card': report_card,
        'student': student,
        'school': school,
        'terms': terms_data,
    }


def generate_report_card_pdf(report_card, save_to_model=True):
    """
    Generate a PDF for one student's report card.

    Args:
        report_card: ReportCard instance
        save_to_model: if True, saves PDF file to report_card.pdf_file

    Returns:
        bytes: raw PDF bytes
    """
    try:
        from weasyprint import HTML, CSS
    except ImportError:
        raise ImportError(
            "WeasyPrint is not installed. Run: pip install weasyprint"
        )

    template_name = TEMPLATE_MAP.get(report_card.report_type)
    if not template_name:
        raise ValueError(f"Unknown report_type: {report_card.report_type}")

    context = build_report_card_context(report_card)
    html_string = render_to_string(template_name, context)
    pdf_bytes = HTML(string=html_string).write_pdf()

    if save_to_model:
        from django.core.files.base import ContentFile
        filename = (
            f"RC_{report_card.report_type.upper()}_"
            f"{report_card.student.student_id}_"
            f"T{report_card.term.term}_"
            f"{report_card.term.academic_year.replace('-','_')}.pdf"
        )
        report_card.mark_generated(ContentFile(pdf_bytes, name=filename))

    return pdf_bytes


def generate_bulk_pdf(school_class, term):
    """
    Generate one merged PDF containing all students' report cards
    in a given class for a given term.

    Args:
        school_class: SchoolClass instance
        term: AcademicTerm instance

    Returns:
        bytes: merged PDF bytes (all students concatenated)
    """
    try:
        from weasyprint import HTML
    except ImportError:
        raise ImportError("WeasyPrint is not installed.")

    from academics.models_grades import ReportCard
    from students.models import Student

    # Determine report_type from class section_type
    rtype = _resolve_report_type(school_class)

    # Get or create report cards for all active students in this class
    students = Student.objects.filter(
        current_class=school_class, status='active'
    ).order_by('last_name', 'first_name')

    if not students.exists():
        raise ValueError(f"No active students found in class {school_class}")

    pdf_documents = []

    for student in students:
        rc, _ = ReportCard.objects.get_or_create(
            student=student,
            term=term,
            defaults={
                'school_class': school_class,
                'report_type': rtype,
            }
        )
        template_name = TEMPLATE_MAP[rtype]
        context = build_report_card_context(rc)
        html_string = render_to_string(template_name, context)
        pdf_documents.append(HTML(string=html_string).render())

    # Merge all pages into one PDF
    from weasyprint import Document
    all_pages = []
    for doc in pdf_documents:
        all_pages.extend(doc.pages)

    # Use first document as base
    merged_pdf = pdf_documents[0].copy(all_pages)
    return merged_pdf.write_pdf()


def _resolve_report_type(school_class):
    """
    Determine the correct report card type from a SchoolClass instance.

    Nursery levels: pre_nursery, nursery1, nursery2, moyenne_section, grande_section
    Primary levels: sil, cp, ce1, ce2, cm1, cm2, class1..class6

    Section types: english → ap/an  |  french / bilingual → fp/fn
    """
    NURSERY_LEVELS = {
        'pre_nursery', 'nursery1', 'nursery2',
        'moyenne_section', 'grande_section'
    }
    level     = getattr(school_class, 'level', '')
    sec_type  = getattr(school_class, 'section_type', 'english')
    is_nursery = level in NURSERY_LEVELS

    if sec_type == 'english':
        return 'an' if is_nursery else 'ap'
    else:
        return 'fn' if is_nursery else 'fp'
