# exams/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth import authenticate, login, get_user_model
from django.http import JsonResponse, HttpResponse
from django.views.decorators.http import require_GET
from django.db.models import Avg, Count, Sum, Q, FloatField, F
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash
from django.contrib.auth.forms import PasswordChangeForm
 
from django.db import transaction

from django.http import HttpResponseForbidden

from django.template.loader import render_to_string

from django.db.models.functions import Coalesce
from django.core.paginator import Paginator


from django.views.decorators.http import require_POST
from django.db import connection

from exams.forms import ExamTypeForm

# from slugify import slugify
# from django.utils.text import slugify
import pprint

from io import BytesIO
import csv, io
import logging
import math

# from xhtml2pdf import pisa

# Excel (openpyxl)
import openpyxl
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
# PDF (ReportLab)
from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Flowable
)
from reportlab.lib.pagesizes import A4, A3, landscape
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import mm, inch
# Excel

# Forms
from .forms import UserUpdateForm, ProfileUpdateForm



from exams.views_helpers import (build_region_summary_context,
build_school_rank_data,
compute_district_ranking,build_best_worst_tables,
build_ward_rank_data,get_region_subject_summary,
get_district_subject_ranking,
get_regional_subject_school_ranking,
build_district_ward_ranking_context,
build_district_school_ranking_context,
build_district_top_bottom_context,
build_district_best_worst_tables,
build_district_subject_summary,
get_district_subject_school_ranking,
build_district_trend,
build_ward_trend, 
build_school_trend,
build_ward_summary_context,
build_school_summary_context,
build_school_subject_performance,
build_school_trend,
compute_school_trend,
compute_region_trend,
build_school_subject_summary_context,
compute_region_school_rank,
compute_region_school_rank_ext,
compute_subject_district_rank,
compute_subject_school_rank,
compute_subject_school_rank_ext,
build_district_summary_context,
build_district_trend,
compute_district_school_rank,
compute_district_school_rank_ext,
compute_subject_dis_school_rank,
compute_subject_dis_school_rank_ext,
compute_school_subject_trend,
compute_district_subject_trend,
compute_region_subject_trend,
build_school_trend_context
) # your existing helper


from exams.app_utils.pdf.pdf_tables import (
    pdf_header_block_simple, pdf_students_block, pdf_grades_block,
    pdf_ownership_block, pdf_district_summary_block, pdf_footer_block,
    pdf_school_ranking_block,pdf_ward_ranking_table,
    simple_rank_table
)
from exams.app_utils.pdf.autofit import PAGE_USABLE_WIDTH
# Models

import os, tempfile
from decimal import Decimal, ROUND_HALF_UP

from exams.app_utils.pdf.pdf_tables import pdf_best_worst_group
from exams.app_utils.pdf.styles import VerticalText

from .models import (
    Region, District, Ward, School,
    ExamType, ExamSubject,
    ExamCandidate, StudentSubjectMark,
    SchoolYearSummary, UserProfile, SchoolSubjectPerformance,ExamSubject,
    SchoolDivisionSummary,SchoolSubjectGradeSummary
)



from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .models import (
    AcademicLevel,
    JointExamProject,
    GradingRule,
    DivisionRule,
    GpaRule,
    ExamCombination,
    ProjectStudent,
    ExamSubjectPaper,
    CombinationPaper,
    SubjectProcessingRule,
    UploadProgress,
    StudentFinalResult,
    ProcessedSubjectScore,   # ✅ ADD THIS
    ExamScore
)
from .forms import (
    AcademicLevelForm,
    JointExamProjectForm,
    GradingRuleForm,
    DivisionRuleForm,
    ExamScoreForm
)


User = get_user_model()

def pct(part, whole):
    try:
        return round((part / whole) * 100, 2) if whole else 0
    except Exception:
        return 0

def round_no_decimal(value):
    if value is None or value == "":
        return ""
    return int(Decimal(str(value)).quantize(0, rounding=ROUND_HALF_UP))


def gpa_to_grade_status(gpa):
    if 1.0000 <= gpa <= 1.5999: return "A","EXCELLENT"
    if 1.6000 <= gpa <= 2.5999: return "B","VERY GOOD"
    if 2.6000 <= gpa <= 3.5999: return "C","GOOD"
    if 3.6000 <= gpa <= 4.5999: return "D","SATISFACTORY"
    if 4.6000 <= gpa <= 5.0000: return "F","FAIL"
    return "-", "-"

def safe_sheet_title(value: str) -> str:
    """
    Make Excel-safe sheet title (max 31 chars, no illegal chars)
    """
    if not value:
        return "Sheet1"

    # Replace illegal Excel characters
    for ch in ['\\', '/', '*', '?', ':', '[', ']']:
        value = value.replace(ch, '-')

    return value[:31]


SUBJECT_GPA_RULES = {
    1: {"A":1,"B":2,"C":3,"D":4,"F":5},
    2: {"A":1,"B":2,"C":3,"D":4,"E":5,"S":6,"F":7},
    3: {"A":1,"B":2,"C":3,"D":4,"E":5},
}


DIVISION_GPA_RULES = {
    "I": 1,
    "II": 2,
    "III": 3,
    "IV": 4,
    "0": 5,
}


def resolve_average_grade(avg, grading_rules):
    for r in grading_rules:
        if r.min_score <= avg <= r.max_score:
            return r.grade
    return "X"


def calculate_grade_point(score, academic_level_id):
    rule = GradingRule.objects.filter(
        academic_level_id=academic_level_id,
        min_score__lte=score,
        max_score__gte=score
    ).first()

    if not rule:
        return "F", 0

    return rule.grade, rule.point

# --------------------------------------------------------------
# AUTH: REGISTER
# --------------------------------------------------------------
def register_page(request):
    if request.method == "POST":
        username = request.POST.get("username")
        email = request.POST.get("email")
        password = request.POST.get("password")

        if User.objects.filter(username=username).exists():
            return render(request, "exams/register.html", {
                "error": "Username already exists"
            })

        User.objects.create_user(username=username, email=email, password=password)
        return render(request, "exams/register.html", {
            "message": "User registered successfully!"
        })
    return render(request, "exams/register.html")


# --------------------------------------------------------------
# AUTH: LOGIN
# --------------------------------------------------------------
from django.contrib.auth import login
from django.contrib.auth.models import User
from django.shortcuts import render, redirect

def login_view(request):
    if request.method == "POST":
        username = request.POST.get("username")
        password = request.POST.get("password")

        user = User.objects.filter(username=username).first()

        # ❌ USER NOT FOUND
        if not user:
            return render(
                request,
                "exams/login.html",
                {"error": "Invalid username or password"}
            )

        # ❌ WRONG PASSWORD
        if not user.check_password(password):
            return render(
                request,
                "exams/login.html",
                {"error": "Invalid username or password"}
            )

        # 🚫 INACTIVE ACCOUNT
        if not user.is_active:
            return render(
                request,
                "exams/login.html",
                {"error": "Your account is OFF. Contact/SMS to 0763169695."}
            )

        # ✅ LOGIN
        login(request, user)
        return redirect("exams:dashboard")

    return render(request, "exams/login.html")




from django.contrib.auth import logout
from django.shortcuts import redirect
from django.conf import settings

def logout_view(request):
    logout(request)
    return redirect(settings.LOGIN_URL)
# --------------------------------------------------------------
# DASHBOARD
# --------------------------------------------------------------
@login_required
def dashboard(request):

    user = request.user
    profile = getattr(user, "profile", None)

    years = ["2022", "2023", "2024", "2025"]

    regions = Region.objects.all()
    subjects = ExamSubject.objects.all().order_by("subject_code")

    districts = []
    wards = []
    schools = []

    if profile:
        if profile.role == "region":
            districts = District.objects.filter(region=profile.region)

        elif profile.role == "district":
            districts = District.objects.filter(region=profile.region)
            wards = Ward.objects.filter(district=profile.district)

        elif profile.role == "ward":
            districts = District.objects.filter(region=profile.region)
            wards = Ward.objects.filter(district=profile.district)
            schools = School.objects.filter(ward=profile.ward)

        elif profile.role == "school":
            districts = District.objects.filter(region=profile.region)
            wards = Ward.objects.filter(district=profile.district)
            schools = School.objects.filter(ward=profile.ward)

    exam_types = ExamType.objects.all().order_by("id")
    exams = ExamType.objects.filter(is_active=True).order_by("-year")

    # ✅ ADD PROJECTS HERE
    projects = (
        JointExamProject.objects
        .select_related("academic_level")
        .order_by("-year", "name")
    )

    context = {
        "regions": regions,
        "districts": districts,
        "wards": wards,
        "schools": schools,
        "exam_types": exam_types,
        "subjects": subjects,
        "exams": exams,
        "projects": projects,  # <-- added
        "user": user,
        "years": years,
        "user_profile": profile,
    }

    return render(request, "exams/dashboard.html", context)











# --------------------------------------------------------------
# DASHBOARD dashboard_subjectwise
# --------------------------------------------------------------
@login_required
def dashboard_subjectwise(request):
    user = request.user
    profile = user.profile

    subjects = ExamSubject.objects.only("subject_id", "subject_code", "subject_name_sw")
    exam_types = ExamType.objects.only("id", "name")

    regions = Region.objects.all() if profile.role == "super" else Region.objects.filter(id=profile.region_id)

    districts = []
    wards = []
    schools = []

    if profile.role == "region":
        districts = District.objects.filter(region=profile.region)
    elif profile.role == "district":
        districts = District.objects.filter(region=profile.region)
        wards = Ward.objects.filter(district=profile.district)
    elif profile.role == "ward":
        districts = District.objects.filter(region=profile.region)
        wards = Ward.objects.filter(district=profile.district)
        schools = School.objects.filter(ward=profile.ward)
    elif profile.role == "school":
        schools = School.objects.filter(id=profile.school_id)

    return render(request, "exams/dashboard_subjectwise.html", {
        "subjects": subjects,
        "regions": regions,
        "districts": districts,
        "wards": wards,
        "schools": schools,
        "exam_types": exam_types,
    })

# --------------------------------------------------------------
# CASCADE AJAX
# --------------------------------------------------------------
@login_required
def ajax_get_districts(request, region_id):
    return JsonResponse(
        list(District.objects.filter(region_id=region_id).values("id", "name")),
        safe=False
    )


@login_required
def ajax_get_wards(request, district_id):
    return JsonResponse(
        list(Ward.objects.filter(district_id=district_id).values("id", "name")),
        safe=False
    )


@login_required
def ajax_get_schools(request, ward_id):
    # return school_code and name so frontend can use code as id
    return JsonResponse(
        list(School.objects.filter(ward_id=ward_id).values("school_code", "name")),
        safe=False
    )


def validate_role_requirements(role, row, line):
    if role == "region" and not row.get("region_id"):
        return f"Row {line}: region_id required for region role"

    if role == "district" and not (row.get("region_id") and row.get("district_id")):
        return f"Row {line}: region_id & district_id required"

    if role == "ward" and not (row.get("region_id") and row.get("district_id") and row.get("ward_id")):
        return f"Row {line}: region_id, district_id & ward_id required"

    if role == "school" and not (
        row.get("region_id") and row.get("district_id")
        and row.get("ward_id") and row.get("school_code")
    ):
        return f"Row {line}: full location required for school role"

    return None


# manage_users.py
User = get_user_model()
@login_required
@permission_required('auth.add_user', raise_exception=True)
def manage_users(request):
    """
    GET: render manage users page.
    POST: supports two modes:
      - add_single: form fields for one user
      - upload_csv: CSV bulk upload
    """
    regions = Region.objects.all().order_by('name')
    districts = District.objects.all().order_by('name')
    wards = Ward.objects.all().order_by('name')
    schools = School.objects.all().order_by('name')

    if request.method == 'POST':
        mode = request.POST.get('mode')

        if mode == 'add_single':
            username = request.POST.get('username', '').strip()
            email = request.POST.get('email', '').strip()
            password = request.POST.get('password', '').strip()
            role = request.POST.get('role')
            region_id = request.POST.get('region')
            district_id = request.POST.get('district')
            ward_id = request.POST.get('ward')
            school_code = request.POST.get('school')

            # server-side validation depending on role:
            errors = []
            def require_field(cond, msg):
                if not cond:
                    errors.append(msg)

            if not username:
                errors.append('Username is required.')
            if not role:
                errors.append('Role is required.')

            # conditional requirements:
            if role == 'region':
                require_field(region_id, 'Region must be selected for Region Admin.')
            elif role == 'district':
                require_field(region_id, 'Region must be selected for District Admin.')
                require_field(district_id, 'District must be selected for District Admin.')
            elif role == 'ward':
                require_field(region_id, 'Region required for Ward Admin.')
                require_field(district_id, 'District required for Ward Admin.')
                require_field(ward_id, 'Ward required for Ward Admin.')
            elif role == 'school':
                require_field(region_id, 'Region required for School Admin.')
                require_field(district_id, 'District required for School Admin.')
                require_field(ward_id, 'Ward required for School Admin.')
                require_field(school_code, 'School required for School Admin.')

            if errors:
                for e in errors:
                    messages.error(request, e)
                return redirect('exams:manage_users')

            # create user + profile inside a transaction
            try:
                with transaction.atomic():
                    # If password empty, use create_user without password or set unusable
                    if password:
                        user = User.objects.create_user(username=username, email=email, password=password)
                    else:
                        user = User.objects.create_user(username=username, email=email)
                        user.set_unusable_password()
                        user.save()

                    # Create profile
                    profile = UserProfile.objects.create(user=user, role=role)

                    # set relation fields if provided
                    if region_id:
                        profile.region_id = int(region_id)
                    if district_id:
                        profile.district_id = int(district_id)
                    if ward_id:
                        profile.ward_id = int(ward_id)
                    if school_code:
                        # adapt depending on your School PK - here we assume school_code is the identifier
                        try:
                            school_obj = School.objects.get(school_code=school_code)
                            profile.school = school_obj
                        except School.DoesNotExist:
                            messages.warning(request, f"School code {school_code} not found; profile saved without school.")
                    profile.save()

                messages.success(request, f"User {username} created.")
                return redirect('exams:manage_users')

            except Exception as exc:
                messages.error(request, f"Failed to create user: {exc}")
                return redirect('exams:manage_users')


        elif mode == 'upload_csv':

            DEFAULT_PASSWORD = "123456"

            csv_file = request.FILES.get('csv_file')
            if not csv_file:
                messages.error(request, "No CSV uploaded.")
                return redirect('exams:manage_users')

            try:
                data = csv_file.read().decode('utf-8')
                reader = csv.DictReader(io.StringIO(data))

                created = 0
                errors = []

                with transaction.atomic():
                    for i, row in enumerate(reader, start=1):

                        username = str(row.get('username', '')).strip()
                        email = str(row.get('email', '')).strip()
                        role = str(row.get('role', '')).strip()

                        region_id = row.get('region_id')
                        district_id = row.get('district_id')
                        ward_id = row.get('ward_id')
                        school_code = row.get('school_code')

                        # -------------------------
                        # BASIC VALIDATION
                        # -------------------------
                        if not username or not role:
                            errors.append(f"Row {i}: username or role missing.")
                            continue

                        if User.objects.filter(username=username).exists():
                            errors.append(f"Row {i}: username '{username}' already exists.")
                            continue

                        # -------------------------
                        # RESOLVE FOREIGN KEYS FIRST
                        # -------------------------
                        region = Region.objects.filter(id=region_id).first() if region_id else None
                        district = District.objects.filter(id=district_id).first() if district_id else None
                        ward = Ward.objects.filter(id=ward_id).first() if ward_id else None
                        school = School.objects.filter(
                            school_code=str(school_code).strip()
                        ).first() if school_code else None

                        # Optional: role-based enforcement
                        if role == "district" and not district:
                            errors.append(f"Row {i}: invalid or missing district_id")
                            continue

                        if role == "ward" and not ward:
                            errors.append(f"Row {i}: invalid or missing ward_id")
                            continue

                        if role == "school" and not school:
                            errors.append(f"Row {i}: invalid or missing school_code")
                            continue

                        # -------------------------
                        # CREATE USER (SAFE)
                        # -------------------------
                        user = User.objects.create_user(
                            username=username,
                            email=email,
                            password=DEFAULT_PASSWORD
                        )

                        # -------------------------
                        # CREATE PROFILE WITH ALL FKs SET
                        # -------------------------
                        UserProfile.objects.create(
                            user=user,
                            role=role,
                            region=region,
                            district=district,
                            ward=ward,
                            school=school
                        )

                        created += 1

                msg = f"Bulk upload completed: {created} users created."
                if errors:
                    msg += " Errors: " + "; ".join(errors[:5])

                messages.success(request, msg)
                return redirect('exams:manage_users')

            except Exception as e:
                messages.error(request, f"CSV processing failed: {e}")
                return redirect('exams:manage_users')

#bulk_create_excel
        elif mode == "bulk_create_excel":
            wb = openpyxl.load_workbook(request.FILES["excel_file"])
            ws = wb.active

            headers = [c.value for c in ws[1]]
            created, errors = 0, []

            with transaction.atomic():
                for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
                    data = dict(zip(headers, row))

                    username = str(data.get("username", "")).strip()
                    role = str(data.get("role", "")).strip()

                    if not username or not role:
                        errors.append(f"Row {i}: username or role missing")
                        continue

                    if User.objects.filter(username=username).exists():
                        errors.append(f"Row {i}: username exists")
                        continue

                    err = validate_role_requirements(role, data, i)
                    if err:
                        errors.append(err)
                        continue

                    user = User.objects.create_user(
                        username=username,
                        email=data.get("email"),
                        password=data.get("password") or None
                    )

                    profile = UserProfile.objects.create(user=user, role=role)

                    profile.region_id = data.get("region_id") or None
                    profile.district_id = data.get("district_id") or None
                    profile.ward_id = data.get("ward_id") or None

                    if data.get("school_code"):
                        profile.school = School.objects.filter(
                            school_code=data["school_code"]
                        ).first()

                    profile.save()
                    created += 1

            messages.success(
                request,
                f"{created} users created. Errors: {len(errors)}"
            )
            return redirect("exams:manage_users")


        elif mode == "bulk_update_roles_excel":
            wb = openpyxl.load_workbook(request.FILES["excel_file"])
            ws = wb.active

            headers = [c.value for c in ws[1]]
            updated, errors = 0, []

            with transaction.atomic():
                for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
                    data = dict(zip(headers, row))
                    username = str(data.get("username", "")).strip()
                    new_role = str(data.get("new_role", "")).strip()

                    if not username or not new_role:
                        errors.append(f"Row {i}: username or new_role missing")
                        continue

                    try:
                        profile = User.objects.get(username=username).profile
                    except User.DoesNotExist:
                        errors.append(f"Row {i}: user not found")
                        continue

                    err = validate_role_requirements(new_role, data, i)
                    if err:
                        errors.append(err)
                        continue

                    profile.role = new_role
                    profile.region = profile.district = profile.ward = profile.school = None

                    profile.region_id = data.get("region_id") or None
                    profile.district_id = data.get("district_id") or None
                    profile.ward_id = data.get("ward_id") or None

                    if data.get("school_code"):
                        profile.school = School.objects.filter(
                            school_code=data["school_code"]
                        ).first()

                    profile.save()
                    updated += 1

            messages.success(
                request,
                f"{updated} users updated. Errors: {len(errors)}"
            )
            return redirect("exams:manage_users")



        elif mode == "download_template":
            template = request.POST.get("template")

            wb = openpyxl.Workbook()
            ws = wb.active

            if template == "create":
                ws.append([
                    "username","email","role","region_id",
                    "district_id","ward_id","school_code","password"
                ])
                fname = "users_bulk_create.xlsx"

            else:
                ws.append([
                    "username","new_role","region_id",
                    "district_id","ward_id","school_code"
                ])
                fname = "users_bulk_role_update.xlsx"

            response = HttpResponse(
                content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            )
            response["Content-Disposition"] = f'attachment; filename="{fname}"'
            wb.save(response)
            return response







    # GET
    # users = User.objects.all().select_related("profile")
    from django.db.models import Q
    from django.core.paginator import Paginator

    search = request.GET.get("q", "").strip()

    users_qs = (
        User.objects
        .select_related(
            "profile",
            "profile__region",
            "profile__district",
            "profile__ward",
            "profile__school",
        )
        .only("id", "username", "email", "is_active")
        .order_by("username")
    )

    # ✅ SERVER-SIDE SEARCH (GLOBAL)
    if search:
        users_qs = users_qs.filter(
            Q(username__icontains=search) |
            Q(email__icontains=search) |
            Q(profile__role__icontains=search) |
            Q(profile__region__name__icontains=search) |
            Q(profile__district__name__icontains=search) |
            Q(profile__ward__name__icontains=search) |
            Q(profile__school__name__icontains=search)
        )

    paginator = Paginator(users_qs, 50)
    page_number = request.GET.get("page")
    users = paginator.get_page(page_number)



    return render(request, 'exams/manage_users.html', {
        'users': users,
        'search': search,
        'regions': regions,
        'districts': districts,
        'wards': wards,
        'schools': schools,
    })


from django.views.decorators.http import require_POST
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required

@login_required
@require_POST
def toggle_user_active(request, user_id):
    user = get_object_or_404(User, id=user_id)

    if user == request.user:
        return JsonResponse({"ok": False, "error": "Cannot disable yourself"})

    user.is_active = not user.is_active
    user.save(update_fields=["is_active"])

    return JsonResponse({
        "ok": True,
        "active": user.is_active
    })


from django.contrib.auth.models import User
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.http import JsonResponse
from django.shortcuts import get_object_or_404

@login_required
@require_POST
def reset_user_password(request, user_id):
    user = get_object_or_404(User, id=user_id)

    # 🔒 prevent self reset (optional but recommended)
    if user == request.user:
        return JsonResponse(
            {"ok": False, "error": "You cannot reset your own password"},
            status=400
        )

    # 🔑 reset password
    user.set_password("12345678")
    user.save(update_fields=["password"])

    return JsonResponse({"ok": True})



from django.views.decorators.http import require_POST
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User

@login_required
@require_POST
def edit_user_ajax(request, user_id):
    user = get_object_or_404(User, id=user_id)

    # Optional: permission check
    if not request.user.is_superuser:
        return JsonResponse({"ok": False, "error": "Permission denied"}, status=403)

    username = request.POST.get("username", "").strip()
    email = request.POST.get("email", "").strip()
    role = request.POST.get("role", "").strip()
    is_active = request.POST.get("is_active") == "1"

    if not username:
        return JsonResponse({"ok": False, "error": "Username required"})

    # unique username check
    if User.objects.exclude(id=user.id).filter(username=username).exists():
        return JsonResponse({"ok": False, "error": "Username already exists"})

    user.username = username
    user.email = email
    user.is_active = is_active
    user.save(update_fields=["username", "email", "is_active"])

    # update profile role (if you use profile)
    if hasattr(user, "profile") and role:
        user.profile.role = role
        user.profile.save(update_fields=["role"])

    return JsonResponse({
        "ok": True,
        "username": user.username,
        "email": user.email,
        "role": role,
        "active": user.is_active
    })



@login_required
@require_POST
def update_user(request, user_id):
    user = get_object_or_404(User, id=user_id)
    form = UserAdminUpdateForm(request.POST, instance=user)
    if form.is_valid():
        form.save()
        return JsonResponse({"ok": True})
    return JsonResponse({"errors": form.errors}, status=400)


# # ========== AJAX UPDATE USER ==========
# @login_required
# def update_user(request, user_id):
#     if request.method == "POST":
#         user = get_object_or_404(User, pk=user_id)

#         data = request.POST
#         user.username = data.get("username")
#         user.email = data.get("email")
#         user.role = data.get("role")
#         user.save()

#         return JsonResponse({"message": "User updated successfully"})

#     return JsonResponse({"error": "Invalid request"}, status=400)


# ========== DELETE USER ==========
@login_required
def delete_user(request, user_id):
    user = get_object_or_404(User, pk=user_id)
    user.delete()
    return JsonResponse({"message": "User deleted successfully"})


# # ========== RESET PASSWORD ==========
# @login_required
# def reset_user_password(request, user_id):
#     if request.method == "POST":
#         user = get_object_or_404(User, pk=user_id)
#         new_password = request.POST.get("new_password")
#         user.set_password(new_password)
#         user.save()
#         return JsonResponse({"message": "Password updated successfully"})




@login_required
def profile_view(request):
    profile = request.user.profile

    if request.method == "POST":
        form = ProfileUpdateForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save()
            messages.success(request, "Profile updated successfully!")
            return redirect("exams:profile")
    else:
        form = ProfileUpdateForm(instance=profile)

    return render(request, "exams/profile.html", {"form": form})



@login_required
def profile_update(request):
    user = request.user
    profile = user.profile

    if request.method == "POST":
        u_form = UserUpdateForm(request.POST, instance=user)
        p_form = ProfileUpdateForm(request.POST, request.FILES, instance=profile)

        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()

            messages.success(request, "Profile updated successfully!")
            return redirect("exams:profile")

        else:
            # FORM INVALID – RETURN PAGE WITH ERRORS
            messages.error(request, "Please correct the errors below.")
            return render(request, "exams/profile.html", {
                "u_form": u_form,
                "p_form": p_form
            })

    else:
        u_form = UserUpdateForm(instance=user)
        p_form = ProfileUpdateForm(instance=profile)

    return render(request, "exams/profile.html", {
        "u_form": u_form,
        "p_form": p_form
    })



@login_required
def change_password(request):
    if request.method == "POST":
        form = PasswordChangeForm(request.user, request.POST)

        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)
            messages.success(request, "Password updated!")
            return redirect("exams:profile")
    else:
        form = PasswordChangeForm(request.user)

    return render(request, "exams/change_password.html", {"form": form})


























def compute_rank_aggregates(qs):
    """
    Computes all aggregates needed for ranking:
      - total
      - pass_count
      - fail_count
      - pass_pct
      - average score (avg_numeric)
      - grades A–E (female/male/total)
    """

    total = qs.count()
    pass_count = qs.filter(status__iexact="PASS").count()
    fail_count = qs.filter(status__iexact="FAIL").count()

    pass_pct = round((pass_count / total * 100), 1) if total else 0

    avg_row = qs.aggregate(avg=Avg("avg_numeric"))
    avg_score = round(float(avg_row["avg"] or 0), 2)


    # Grade buckets
    def gcount(g, sex=None):
        f = {"avg_grade__iexact": g}
        if sex:
            f["sex__iexact"] = sex
        return qs.filter(**f).count()

    grades = {
        "A_f": gcount("A", "F"), "A_m": gcount("A", "M"),
        "B_f": gcount("B", "F"), "B_m": gcount("B", "M"),
        "C_f": gcount("C", "F"), "C_m": gcount("C", "M"),
        "D_f": gcount("D", "F"), "D_m": gcount("D", "M"),
        "E_f": gcount("E", "F"), "E_m": gcount("E", "M"),
    }

    # Total for each A–E
    grades["A"] = grades["A_f"] + grades["A_m"]
    grades["B"] = grades["B_f"] + grades["B_m"]
    grades["C"] = grades["C_f"] + grades["C_m"]
    grades["D"] = grades["D_f"] + grades["D_m"]
    grades["E"] = grades["E_f"] + grades["E_m"]

    # Pass A–C
    grades["AC_total"] = grades["A"] + grades["B"] + grades["C"]

    return {
        "total": total,
        "pass_count": pass_count,
        "fail_count": fail_count,
        "pass_pct": pass_pct,
        "avg_score": avg_score,
        "grades": grades,
    }





















# -------------------------
# Regionwise listing page
# -------------------------
@login_required
def regionwise(request):

    user = request.user
    years = ["2022", "2023", "2024", "2025"]
    # SUPER ADMIN → ALL REGIONS
    if user.profile.role == "super":
        regions = Region.objects.all().order_by("name")

    # REGION ACCOUNT → ONLY THEIR REGION
    elif user.profile.role == "region":
        regions = Region.objects.filter(id=user.profile.region_id)

    # DISTRICT ACCOUNT → THEIR REGION ONLY
    elif user.profile.role == "district":
        regions = Region.objects.filter(id=user.profile.district.region_id)

    # WARD ACCOUNT → THEIR REGION
    elif user.profile.role == "ward":
        regions = Region.objects.filter(id=user.profile.ward.district.region_id)

    # SCHOOL ACCOUNT → THEIR REGION
    elif user.profile.role == "school":
        regions = Region.objects.filter(id=user.profile.school.region_id)

    else:
        regions = Region.objects.none()

    subjects = ExamSubject.objects.all().order_by("subject_code") 
    # ✅ LOAD EXAM TYPES (ID + NAME)
    exam_types = ExamType.objects.filter(is_active=True).only("id", "name").order_by("-year")

    # ============= AJAX SEARCH ==============
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        query = request.GET.get("q", "").strip().lower()

        results = []
        for r in regions:
            if query in r.name.lower():
                results.append({
                    "id": r.id,
                    "name": r.name,
                    "district_count": r.districts.count(),   # ✅ FIXED HERE
                })

        return JsonResponse({
            "districts": results,
            "exam_types": [
                {"id": e.id, "name": e.name} for e in exam_types
            ]
        })

    # Normal page load
    return render(request, "exams/reports/regionwise.html", {
        "regions": regions,
        "subjects": subjects,
        "years": years,
        "exam_types": exam_types,   # ✅ now available in template
    })



@login_required
def region_school_ranking_html(request):

    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_school_rank(region, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/region_school_rank_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "region": Region.objects.get(id=region)
    })





@login_required
def region_school_ranking_print(request):

    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_school_rank(region, year, exam_type_id)

    return render(request,"exams/reports/region_school_rank_print.html",{
        "rows": rows,
        "year": year,
        "exam_type": ExamType.objects.get(id=exam_type_id),
        "region": Region.objects.get(id=region),
    })


@login_required
def region_school_ranking_excel(request):

    from openpyxl import Workbook
    from django.http import HttpResponse

    rows = compute_region_school_rank(
        request.GET["region"],
        request.GET["year"],
        request.GET["exam_type_id"]
    )

    wb=Workbook()
    ws=wb.active

    ws.append(["#","CODE","NAME","DISTRICT","REGION",
               "I","II","III","IV","0","#I-IV","%I-IV","GPA"])

    for r in rows:
        ws.append([
            r["rank"],r["code"],r["name"],r["district"],r["region"],
            r["d1"],r["d2"],r["d3"],r["d4"],r["d0"],
            r["pass"],r["pass_pct"],r["gpa"]
        ])

    resp=HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    resp["Content-Disposition"]="attachment; filename=Region_School_Rank.xlsx"
    wb.save(resp)
    return resp


@login_required
def region_school_ranking_pdf(request):

    rows = compute_region_school_rank(
        request.GET["region"],
        request.GET["year"],
        request.GET["exam_type_id"]
    )

    from reportlab.platypus import SimpleDocTemplate,Table,TableStyle
    from reportlab.lib.pagesizes import A4
    from reportlab.lib import colors
    from django.http import HttpResponse

    resp=HttpResponse(content_type="application/pdf")
    resp["Content-Disposition"]="attachment; filename=Region_School_Rank.pdf"

    data=[["#","CODE","NAME","DISTRICT","REGION","I","II","III","IV","0","#I-IV","%I-IV","GPA"]]

    for r in rows:
        data.append([
            r["rank"],r["code"],r["name"],r["district"],r["region"],
            r["d1"],r["d2"],r["d3"],r["d4"],r["d0"],
            r["pass"],r["pass_pct"],r["gpa"]
        ])

    table=Table(data,repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID",(0,0),(-1,-1),1,colors.black),
        ("BACKGROUND",(0,0),(-1,0),colors.lightgrey),
        ("ALIGN",(0,0),(-1,-1),"CENTER")
    ]))

    doc=SimpleDocTemplate(resp,pagesize=A4)
    doc.build([table])
    return resp


@login_required
def region_school_ranking_ext_html(request):

    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_school_rank_ext(region, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/region_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "region": Region.objects.get(id=region)
    })






@login_required
def district_school_ranking_ext_html(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank_ext(district, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/district_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "district": Region.objects.get(id=district)
    })



from openpyxl import Workbook
from django.http import HttpResponse

@login_required
def region_school_ranking_ext_excel(request):

    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_school_rank_ext(region, year, exam_type_id)

    wb = Workbook()
    ws = wb.active
    ws.title = "Region Ranking"

    # ---------------- HEADER ROW ----------------
    headers = [
        "#","CNO","SCHOOL NAME","REGION","DISTRICT","OWNERSHIP",

        "REG F","REG M","REG T",

        "SAT F","SAT M","SAT T",

        "I F","I M","I T","I %",

        "II F","II M","II T","II %",

        "III F","III M","III T","III %",

        "PASS I-III F","PASS I-III M","PASS I-III T","PASS I-III %",

        "IV F","IV M","IV T","IV %",

        "FAIL F","FAIL M","FAIL T","FAIL %",

        "PASS I-IV F","PASS I-IV M","PASS I-IV T","PASS I-IV %",

        "GPA","GRADE","STATUS","POSITION"
    ]

    ws.append(headers)

    # ---------------- DATA ROWS ----------------
    for r in rows:
        ws.append([
            r["no"], r["cno"], r["name"], r["region"], r["district"], r["ownership"],

            r["reg_f"], r["reg_m"], r["reg_t"],

            r["sat_f"], r["sat_m"], r["sat_t"],

            r["i_f"], r["i_m"], r["i_t"], r["i_pct"],

            r["ii_f"], r["ii_m"], r["ii_t"], r["ii_pct"],

            r["iii_f"], r["iii_m"], r["iii_t"], r["iii_pct"],

            r["pass_i_iii_f"], r["pass_i_iii_m"], r["pass_i_iii_t"], r["pass_i_iii_pct"],

            r["iv_f"], r["iv_m"], r["iv_t"], r["iv_pct"],

            r["f_f"], r["f_m"], r["f_t"], r["f_pct"],

            r["pass_i_iv_f"], r["pass_i_iv_m"], r["pass_i_iv_t"], r["pass_i_iv_pct"],

            r["gpa"], r["grade"], r["status"], r["position"]
        ])

    # ---------------- RESPONSE ----------------
    filename = f"REGION_SCHOOL_RANK_EXT_{year}.xlsx"

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response


@login_required
def region_school_ranking_ext_print(request):

    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_school_rank_ext(region, year, exam_type_id)

    return render(request,"exams/reports/region_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "region": Region.objects.get(id=region)
    })





















@login_required
def region_ranking(request, region_id):
    year = int(request.GET.get("year", 2024))

    qs = SchoolYearSummary.objects.filter(
        school__region_id=region_id,
        year=year
    ).values(
        "school__name",
        "school__school_code"
    ).annotate(
        avg_mark=Avg("avg_numeric"),
        candidates=Sum("candidates")
    ).order_by("-avg_mark")

    region = Region.objects.get(id=region_id)

    return render(request, "exams/region_ranking.html", {
        "region": region,
        "year": year,
        "ranking": qs
    })


# -----------------------
# Helper: average -> band letter
# -----------------------
def avg_to_band(avg_value):
    """
    Map numeric average (0-300) to band A-E according to user's mapping:
    0-60 -> E
    61-120 -> D
    121-180 -> C
    181-240 -> B
    241-300 -> A
    """
    try:
        v = float(avg_value or 0)
    except Exception:
        return "-"
    if v <= 60:
        return "E"
    if v <= 120:
        return "D"
    if v <= 180:
        return "C"
    if v <= 240:
        return "B"
    return "A"
# -----------------------
# Region summary HTML (you likely already have)
# -----------------------
@login_required
def region_summary(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    if not region_id or not year:
        return render(request, "exams/reports/region_summary.html", {"error": "Please select region and year."})

    region = get_object_or_404(Region, pk=region_id)
    year = int(year)

    cand_qs = ExamCandidate.objects.filter(year=year, school__region_id=region.id)

    # per-district aggregations
    district_stats = (
        cand_qs.values("school__district__id", "school__district__name")
               .annotate(
                   total=Count("id"),
                   pass_count=Count("id", filter=Q(status__iexact="PASS")),
                   fail_count=Count("id", filter=Q(status__iexact="FAIL")),
                   male=Count("id", filter=Q(sex__iexact="M")),
                   female=Count("id", filter=Q(sex__iexact="F")),
                   male_pass=Count("id", filter=Q(sex__iexact="M") & Q(status__iexact="PASS")),
                   female_pass=Count("id", filter=Q(sex__iexact="F") & Q(status__iexact="PASS")),
               )
               .order_by("school__district__name")
    )

    districts = []
    totals = {"total":0, "pass":0, "fail":0, "male":0, "female":0}
    for row in district_stats:
        total = row["total"] or 0
        pc = row["pass_count"] or 0
        fc = row["fail_count"] or 0
        male = row["male"] or 0
        female = row["female"] or 0
        male_pass = row.get("male_pass") or 0
        female_pass = row.get("female_pass") or 0
        pass_pct = round((pc / total * 100), 1) if total else 0
        districts.append({
            "district_name": row.get("school__district__name") or "Unknown",
            "total": total,
            "pass": pc,
            "fail": fc,
            "pass_pct": pass_pct,
            "male": male,
            "female": female,
            "male_pass": male_pass,
            "female_pass": female_pass,
        })
        totals["total"] += total
        totals["pass"] += pc
        totals["fail"] += fc
        totals["male"] += male
        totals["female"] += female

    totals["pass_pct"] = round((totals["pass"] / totals["total"] * 100), 1) if totals["total"] else 0

    # header numbers
    registered = cand_qs.count()
    pass_fail = cand_qs.filter(status__in=["PASS","pass","Pass","FAIL","fail","Fail"]).count()
    other_status = registered - pass_fail

    # region average — use SchoolYearSummary average of avg_numeric (if available for this year)
    school_summary_qs = SchoolYearSummary.objects.filter(year=year, school__region_id=region.id)
    total_schools = school_summary_qs.values_list("school_id", flat=True).distinct().count()
    region_avg = school_summary_qs.aggregate(avg=Avg("avg_numeric"))["avg"] or 0
    region_avg_rounded = round(region_avg, 4)
    region_avg_band = avg_to_band(region_avg_rounded)

    context = {
        "region": region,
        "year": year,
        "districts": districts,
        "totals": totals,
        "registered": registered,
        "pass_fail": pass_fail,
        "other_status": other_status,
        "region_avg": region_avg_rounded,
        "region_avg_band": region_avg_band,
        "total_schools": total_schools,
    }
    return render(request, "exams/reports/region_summary.html", context)






# View: redesigned HTML preview
# -----------------------
# View: PDF export using xhtml2pdf
def _on_page(canvas, doc):
    # simple page number footer at bottom center
    canvas.saveState()
    canvas.setFont("Helvetica", 9)
    page_no = f"Page {doc.page}"
    canvas.drawString(30, 20, page_no)
    canvas.restoreState()

@login_required
def region_summary_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    if not region_id or not year:
        return HttpResponse("Missing parameters", status=400)

    context = build_region_summary_context(region_id, year)

    # PDF response
    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = f'inline; filename="Region_{year}.pdf"'

    # Use SimpleDocTemplate with small margins
    doc = SimpleDocTemplate(response, pagesize=A4,
                            leftMargin=20, rightMargin=20,
                            topMargin=20, bottomMargin=30)

    story = []

    # Header (single table flowable)
    
    hdr = pdf_header_block_simple(context["region"], year)
    story.append(hdr)
    story.append(Spacer(1, 8))

    # Students block
    story.append(pdf_students_block(context.get("totals", {})))
    story.append(Spacer(1, 10))

    # Grades summary
    story.append(pdf_grades_block(context.get("grades", {}), context.get("totals", {})))
    story.append(Spacer(1, 12))

    # Ownership blocks (each one spaced)
    for tbl in pdf_ownership_block(context.get("ownership", [])):
        story.append(tbl)
        story.append(Spacer(1, 12))

    # District summary table last (if present)
    if context.get("districts"):
        story.append(pdf_district_summary_block(context.get("districts", []), context.get("districts_totals", {})))
        story.append(Spacer(1, 8))

    # Footer text (example)
    footer_text = f"STD-VII : {year} - MKOA - {getattr(context['region'], 'name', '')}"
    story.append(Spacer(1, 20))
    story.append(pdf_footer_block(footer_text))

    # Build
    doc.build(story, onFirstPage=_on_page, onLaterPages=_on_page)
    return response





# -----------------------
# Region Excel export using openpyxl
# -----------------------
@login_required
def region_summary_excel(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    ctx = build_region_summary_context(region_id, year, exam_type_id)

    wb = Workbook()
    ws = wb.active
    ws.title = "Region Summary"

    row = 1

    def write_row(values):
        nonlocal row
        for col, v in enumerate(values, 1):
            ws.cell(row=row, column=col, value=v)
        row += 1

    write_row(["PRIME MINISTER'S OFFICE - RALG"])
    write_row([f"FORM TWO NATIONAL ASSESSMENT {ctx['year']}"])
    write_row([f"REGION: {ctx['region'].name}"])
    write_row([])

    write_row(["STUDENTS REGISTRATION AND PERFORMANCE"])
    write_row(["", "FEMALE", "", "MALE", "", "TOTAL"])
    write_row(["REGISTERED", ctx["totals"]["reg_f"], "", ctx["totals"]["reg_m"], "", ctx["totals"]["reg"]])
    write_row(["WITHs RESULTS", ctx["totals"]["sat_f"], "", ctx["totals"]["sat_m"], "", ctx["totals"]["sat"]])
    write_row(["ABSENTEES", ctx["totals"]["abs_f"], "", ctx["totals"]["abs_m"], "", ctx["totals"]["abs"]])
    write_row(["REGION GPA", ctx["totals"]["avg"], ctx["totals"]["grade"], ctx["totals"]["status"]])
    write_row([])

    write_row(["DIVISION PERFORMANCE"])
    write_row(["SEX","I","II","III","IV","0","PASS I-III","PASS I-IV"])

    write_row([
        "F",
        ctx["grades"]["af"],
        ctx["grades"]["bf"],
        ctx["grades"]["cf"],
        ctx["grades"]["df"],
        ctx["grades"]["ef"],
        ctx["a_c_f"],
        ctx["a_d_f"]
    ])

    write_row([
        "M",
        ctx["grades"]["am"],
        ctx["grades"]["bm"],
        ctx["grades"]["cm"],
        ctx["grades"]["dm"],
        ctx["grades"]["em"],
        ctx["a_c_m"],
        ctx["a_d_m"]
    ])

    write_row([
        "T",
        ctx["grades"]["a"],
        ctx["grades"]["b"],
        ctx["grades"]["c"],
        ctx["grades"]["d"],
        ctx["grades"]["e"],
        ctx["a_c_total"],
        ctx["a_d_total"]
    ])

    write_row([])

    write_row(["DISTRICT PERFORMANCE"])
    write_row([
        "#","NAME","SAT",
        "I(F)","I(M)","I(T)",
        "II(F)","II(M)","II(T)",
        "III(F)","III(M)","III(T)",
        "IV(F)","IV(M)","IV(T)",
        "0(F)","0(M)","0(T)",
        "PASS","PASS %","GPA","GRADE","STATUS"
    ])

    for d in ctx["district_perf"]:
        write_row([
            d["no"], d["name"], d["sat"],
            d["af"], d["am"], d["at"],
            d["bf"], d["bm"], d["bt"],
            d["cf"], d["cm"], d["ct"],
            d["df"], d["dm"], d["dt"],
            d["ff"], d["fm"], d["ft"],
            d["pass_no"], d["pass_pct"],
            d["gpa"], d["grade"], d["status"]
        ])

    write_row([])

    write_row(["SUBJECT PERFORMANCE"])
    write_row([
        "CODE","NAME","SAT",
        "A(F)","A(M)","A(T)",
        "B(F)","B(M)","B(T)",
        "C(F)","C(M)","C(T)",
        "D(F)","D(M)","D(T)",
        "F(F)","F(M)","F(T)",
        "PASS","PASS %","GPA","GRADE","STATUS"
    ])

    for s in ctx["subjects"]:
        write_row([
            s["code"], s["subject"], s["sat"],
            s["af"], s["am"], s["at"],
            s["bf"], s["bm"], s["bt"],
            s["cf"], s["cm"], s["ct"],
            s["df"], s["dm"], s["dt"],
            s["ff"], s["fm"], s["ft"],
            s["pass_no"], s["pass_pct"],
            s["gpa"], s["grade"], s["status"]
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    filename = f"Region_Summary_{ctx['region'].id}_{ctx['year']}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response


@login_required
def region_summary_html(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    profile = getattr(request.user, "profile", None)

    if profile and profile.role != "super":
        if profile.role == "region":
            if int(region_id) != profile.region_id:
                return HttpResponseForbidden("Access denied")
        else:
            return HttpResponseForbidden("Access denied")

    ctx = build_region_summary_context(
        region_id, year, exam_type_id
    )
    return render(request, "exams/reports/region_summary_html.html", ctx)




@login_required
def region_trend_html(request):

    region_id = request.GET.get("region")
    start = request.GET.get("start")
    end = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_trend(region_id, start, end, exam_type_id)

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    exam = ExamType.objects.get(id=exam_type_id)
    return render(request, "exams/reports/region_trend_html.html", {
        "region": region,
        "exam": exam,
        "rows": rows,
        "start": start,
        "end": end,
        "exam_type": exam_type,
        "exam_type_id": exam_type_id
    })




@login_required
def region_trend_print(request):

    region_id = request.GET.get("region")
    start_year = request.GET.get("start")
    end_year = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_trend(region_id, start_year, end_year, exam_type_id)

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    exam = ExamType.objects.get(id=exam_type_id)
    return render(request, "exams/reports/region_trend_print.html", {
        "region": region,
        "exam": exam,
        "rows": rows,
        "start": start_year,
        "end": end_year,
        "exam_type": exam_type,
        "exam_type_id": exam_type_id
    })



@login_required
def district_trend_print(request):

    district_id = request.GET.get("district")
    start_year = request.GET.get("start")
    end_year = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = build_district_trend(district_id, start_year, end_year, exam_type_id)

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    
    return render(request, "exams/reports/district_trend_print.html", {
        "district": district,
        "rows": rows,
        "start": start_year,
        "end": end_year,
        "exam_type": exam_type,
        "exam_type_id": exam_type_id
    })



@login_required
def region_summary_print(request):
    region = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    ctx = build_region_summary_context(region, year, exam_type_id)
    return render(request, "exams/reports/region_summary_print.html", ctx)



@login_required
def district_summary_print(request):
    district = request.GET.get("district")
    year = request.GET.get("year")

    exam_type_id = request.GET.get("exam_type_id")
    exam_type = ExamType.objects.get(id=exam_type_id)

    ctx = build_district_summary_context(district, year, exam_type_id)
    return render(request, "exams/reports/district_summary_print.html", ctx)




@login_required
def district_ranking_html(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    context = compute_district_ranking(region_id, year)

    return render(
        request,
        "exams/reports/district_ranking_html.html",
        {
            "region": context["region"],
            "year": year,
            "districts": context["rows"],   # <-- IMPORTANT
            "totals": context["totals"],
        }
    )







@login_required
def district_ranking_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    context = compute_district_ranking(region_id, year)
    region = context["region"]
    region_name = region.name if hasattr(region, "name") else str(region)

    # Fix tuple rows → dict rows
    clean_rows = []
    for r in context["rows"]:
        if isinstance(r, dict):
            clean_rows.append(r)
        else:
            # tuple → dict conversion (assumes standardized order)
            clean_rows.append({
                "district": r[1],
                "total": r[2],
                "pass": r[3],
                "fail": r[4],
                "pass_pct": r[5],
                "avg_grade": r[6] if len(r) > 6 else "",
                "status": r[7] if len(r) > 7 else "",
            })

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f"inline; filename=District_Rank_{region_name}_{year}.pdf"
    )

    doc = SimpleDocTemplate(response)
    story = []

    story.append(pdf_header_block_simple(region, year))
    story.append(Spacer(1, 10))

    # Use the cleaned rows
    story.append(pdf_district_summary_block(context["rows"], context["totals"]))

    doc.build(story)
    return response





@login_required
def district_ranking_excel(request):

    context = compute_district_ranking(
        request.GET.get("region"),
        request.GET.get("year")
    )

    region = context["region"]
    districts = context["rows"]
    totals = context["totals"]

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "District Ranking"

    ws.append([
        "District", "A-F", "A-M", "A-T",
        "B-F", "B-M", "B-T",
        "C-F", "C-M", "C-T",
        "D-F", "D-M", "D-T",
        "E-F", "E-M", "E-T",
        "PASS F", "PASS M", "PASS T",
        "%PASS", "Avg Score", "Total Std", "Schools"
    ])

    for d in districts:
        ws.append([
            d["district"],
            d["af"], d["am"], d["a"],
            d["bf"], d["bm"], d["b"],
            d["cf"], d["cm"], d["c"],
            d["df"], d["dm"], d["d"],
            d["ef"], d["em"], d["e"],
            d["pass_f"], d["pass_m"], d["pass_t"],
            d["pass_ac_pct"], d["avg_score"],
            d["total_std"], d["total_schools"]
        ])

    # ADD TOTAL ROW
    ws.append([
        "TOTAL",
        totals["af"], totals["am"], totals["a"],
        totals["bf"], totals["bm"], totals["b"],
        totals["cf"], totals["cm"], totals["c"],
        totals["df"], totals["dm"], totals["d"],
        totals["ef"], totals["em"], totals["e"],
        totals["pass_f"], totals["pass_m"], totals["pass_t"],
        "-", totals["avg_score"],
        totals["total_std"], totals["total_schools"]
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = "attachment; filename=district_ranking.xlsx"

    wb.save(response)
    return response















# --------------------------------------------------------------
# EVERYTHING-WARD
# --------------------------------------------------------------
@login_required
def ward_ranking_html(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year", 2024))

    region = Region.objects.get(id=region_id)

    wards, totals = build_ward_rank_data(region_id, year)

    return render(request, "exams/reports/ward_ranking_html.html", {
        "region": region,
        "year": year,
        "wards": wards,
        "totals": totals
    })


@login_required
def ward_ranking_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    region = get_object_or_404(Region, pk=region_id)
    wards, totals = build_ward_rank_data(region_id, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Ward_Ranking_{region.name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=landscape(A4),
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=20
    )

    story = []
    story.append(pdf_ward_ranking_table(region, year, wards))

    doc.build(story)
    return response


@login_required
def ward_ranking_excel(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year", 2024))

    region = Region.objects.get(id=region_id)

    wards, totals = build_ward_rank_data(region_id, year)

    wb = Workbook()
    ws = wb.active
    ws.title = "Ward Ranking"

    headers = [
        "Rank","Ward",
        "A(F)","A(M)","A(T)",
        "B(F)","B(M)","B(T)",
        "C(F)","C(M)","C(T)",
        "D(F)","D(M)","D(T)",
        "E(F)","E(M)","E(T)",
        "Total Std","Schools",
        "Pass(F)","Pass(M)","Pass(T)",
        "Avg Score"
    ]
    ws.append(headers)

    for w in wards:
        ws.append([
            w["rank"], w["name"],
            w["af"], w["am"], w["a"],
            w["bf"], w["bm"], w["b"],
            w["cf"], w["cm"], w["c"],
            w["df"], w["dm"], w["d"],
            w["ef"], w["em"], w["e"],
            w["total_std"],
            w["total_schools"],
            w["pass_f"], w["pass_m"], w["pass_t"],
            w["avg_score"]
        ])

    response = HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    response["Content-Disposition"] = f"attachment; filename=ward_ranking_{region.name}_{year}.xlsx"
    wb.save(response)
    return response


@login_required
def ward_ranking(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    district_id = request.GET.get("district")  # optional: restrict to a district
    page = int(request.GET.get("page", 1))

    if not region_id or not year:
        return render(request, "exams/error.html", {"message":"Missing region or year"})

    region = get_object_or_404(Region, pk=region_id)
    year = int(year)

    base_qs = ExamCandidate.objects.filter(year=year, school__region_id=region.id)

    ward_qs = Ward.objects.filter(district__region_id=region.id)
    if district_id:
        ward_qs = ward_qs.filter(district_id=district_id)

    ranking_rows = []
    for w in ward_qs:
        w_qs = base_qs.filter(school__ward_id=w.id)
        agg = compute_rank_aggregates(w_qs)
        ranking_rows.append({
            "ward": w,
            "total": agg["total"],
            "pass_count": agg["pass_count"],
            "fail_count": agg["fail_count"],
            "pass_pct": agg["pass_pct"],
            "avg_score": agg["avg_score"],
            "grades": agg["grades"],
        })

    ranking_rows.sort(key=lambda r: (r["pass_pct"], r["avg_score"], r["pass_count"]), reverse=True)

    paginator = Paginator(ranking_rows, 50)
    page_obj = paginator.get_page(page)

    return render(request, "exams/reports/ward_ranking.html", {
        "region": region,
        "year": year,
        "district_id": district_id,
        "page_obj": page_obj,
    })






















@login_required
def school_ranking_html(request):
    # expects region & year (district/ward optional)
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    district_id = request.GET.get("district")
    ward_id = request.GET.get("ward")

    if not region_id or not year:
        return render(request, "exams/error.html", {"message":"Missing region or year"})

    region = get_object_or_404(Region, pk=region_id)
    schools, totals = build_school_rank_data(region_id, year, district_id, ward_id)

    return render(request, "exams/reports/school_ranking_html.html", {
        "region": region,
        "year": int(year),
        "schools": schools,
        "totals": totals,
    })



@login_required
def best_schools_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    if not region_id or not year:
        return HttpResponse("Missing parameters", status=400)

    # MUST load region object (pdf_header_block_simple expects an object not ID)
    region = get_object_or_404(Region, pk=region_id)

    # PDF response
    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = f'inline; filename="Best_Schools_{region.name}_{year}.pdf"'

    doc = SimpleDocTemplate(response)
    story = []

    # HEADER BLOCK
    story.append(pdf_header_block_simple(region, year))
    story.append(Spacer(1, 10))

    # BEST & WORST TABLE BLOCKS (Top10, Bottom10, etc.)
    blocks = pdf_best_worst_group(region_id, year)
    for b in blocks:
        story.append(b)

    # Build final PDF
    doc.build(story)
    return response






@login_required
def school_ranking_excel(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    district_id = request.GET.get("district")
    ward_id = request.GET.get("ward")

    if not region_id or not year:
        return HttpResponse("Missing parameters", status=400)

    region = get_object_or_404(Region, pk=region_id)
    schools, totals = build_school_rank_data(region_id, year, district_id, ward_id)

    wb = Workbook()
    ws = wb.active
    ws.title = "School Ranking"

    headers = [
        "Rank","School Code","School Name",
        "A(F)","A(M)","A(T)",
        "B(F)","B(M)","B(T)",
        "C(F)","C(M)","C(T)",
        "D(F)","D(M)","D(T)",
        "E(F)","E(M)","E(T)",
        "Total Std","Pass F","Pass M","Pass T","% Pass","Avg Score"
    ]
    ws.append(headers)

    for s in schools:
        ws.append([
            s["rank"], s["school_code"], s["name"],
            s["af"], s["am"], s["a"],
            s["bf"], s["bm"], s["b"],
            s["cf"], s["cm"], s["c"],
            s["df"], s["dm"], s["d"],
            s["ef"], s["em"], s["e"],
            s["total_std"],
            s["pass_f"], s["pass_m"], s["pass_t"],
            s["pass_pct"],
            s["avg_score"]
        ])

    # totals row
    ws.append([
        "TOTAL", "", "",
        totals["af"], totals["am"], totals["a"],
        totals["bf"], totals["bm"], totals["b"],
        totals["cf"], totals["cm"], totals["c"],
        totals["df"], totals["dm"], totals["d"],
        totals["ef"], totals["em"], totals["e"],
        totals["total_std"],
        totals["pass_f"], totals["pass_m"], totals["pass_t"],
        "-", totals["avg_score"]
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    fname = f"school_ranking_{(region.name)}_{year}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{fname}"'
    wb.save(response)
    return response
    
@login_required
def school_ranking_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    district_id = request.GET.get("district")  # optional
    ward_id = request.GET.get("ward")  # optional
    page = int(request.GET.get("page", 1))

    if not region_id or not year:
        return render(request, "exams/error.html", {"message":"Missing region or year"})

    region = get_object_or_404(Region, pk=region_id)
    year = int(year)

    base_qs = ExamCandidate.objects.filter(year=year, school__region_id=region.id)

    # iterate schools with candidates OR use a join to distinct school ids
    school_ids = base_qs.values_list("school_id", flat=True).distinct()
    schools = School.objects.filter(id__in=school_ids)
    if district_id:
        schools = schools.filter(district_id=district_id)
    if ward_id:
        schools = schools.filter(ward_id=ward_id)

    ranking_rows = []
    for s in schools:
        s_qs = base_qs.filter(school_id=s.id)
        agg = compute_rank_aggregates(s_qs)
        ranking_rows.append({
            "school": s,
            "total": agg["total"],
            "pass_count": agg["pass_count"],
            "fail_count": agg["fail_count"],
            "pass_pct": agg["pass_pct"],
            "avg_score": agg["avg_score"],
            "grades": agg["grades"],
        })

    ranking_rows.sort(key=lambda r: (r["pass_pct"], r["avg_score"], r["pass_count"]), reverse=True)

    paginator = Paginator(ranking_rows, 100)
    page_obj = paginator.get_page(page)

    return render(request, "exams/reports/school_ranking.html", {
        "region": region,
        "year": year,
        "page_obj": page_obj,
    })


















@login_required
def best_schools_tables(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    if not region_id or not year:
        return HttpResponse("Missing parameters", status=400)

    region = get_object_or_404(Region, pk=region_id)

    data = build_best_worst_tables(region_id, year)

    return render(request, "exams/reports/best10_tables.html", {
        "region": region,
        "year": year,
        **data
    })


@login_required
def best_schools_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    if not region_id or not year:
        return HttpResponse("Missing parameters", status=400)

    # MUST load region object (pdf_header_block_simple expects an object not ID)
    region = get_object_or_404(Region, pk=region_id)

    # PDF response
    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = f'inline; filename="Best_Schools_{region.name}_{year}.pdf"'

    doc = SimpleDocTemplate(response)
    story = []

    # HEADER BLOCK
    story.append(pdf_header_block_simple(region, year))
    story.append(Spacer(1, 10))

    # BEST & WORST TABLE BLOCKS (Top10, Bottom10, etc.)
    blocks = pdf_best_worst_group(region_id, year)
    for b in blocks:
        story.append(b)

    # Build final PDF
    doc.build(story)
    return response









@login_required
def best_schools_excel(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")

    region = get_object_or_404(Region, pk=region_id)
    tables = build_best_worst_tables(region_id, year)

    wb = Workbook()

    def add_sheet(title, rows):
        ws = wb.create_sheet(title)
        headers = [
            "CODE","NAME","DISTRICT","TOTAL",
            "A","B","C","D","E",
            "PASS A-C","% PASS","AVERAGE","AVG GRADE","STATUS"
        ]
        ws.append(headers)

        for r in rows:
            ws.append([
                r["school_code"], r["name"], r["district"], r["total"],
                r["A"], r["B"], r["C"], r["D"], r["E"],
                r["pass_ac"], r["pass_pct"],
                r["avg"], r["avg_grade"], r["status"],
            ])

    add_sheet("Top10", tables["top10"])
    add_sheet("Bottom10", tables["bottom10"])
    add_sheet("Top10_30plus", tables["top10_big"])
    add_sheet("Bottom10_30plus", tables["bottom10_big"])
    add_sheet("Top10_Gov", tables["top10_gov"])

    # remove default empty sheet
    if "Sheet" in wb.sheetnames:
        wb.remove(wb["Sheet"])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="best10_{region.name}_{year}.xlsx"'
    wb.save(response)
    return response










# --------------------------------------------------------------
# SUBJECTS
# --------------------------------------------------------------
@login_required
def general_subject_summary(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    region, summary = get_region_subject_summary(region_id, year, subject)

    return render(request, "exams/reports/subject_summary.html", {
        "summary": summary,
        "subject": subject,
        "year": year,
        "region": region,
    })





@login_required
def region_subject_summary_pdf(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    region, summary = get_region_subject_summary(region_id, year, subject)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Subject_Summary_{region.name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response, pagesize=A4,
        leftMargin=20, rightMargin=20,
        topMargin=20, bottomMargin=30
    )

    story = []
    styles = getSampleStyleSheet()

    story.append(Paragraph(
        f"<b>SUBJECT SUMMARY – {year}</b><br/>{region.name}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    data = [[
        "Subject", "Name", "Total",
        "A", "B", "C", "D", "E",
        "Pass A–C", "% Pass"
    ]]

    for r in summary:
        data.append([
            r["subject_code"],
            r["subject_name_sw"],
            r["total_std"],
            r["A"], r["B"], r["C"], r["D"], r["E"],
            r["pass_ac"],
            f'{r["pass_pct"]}%',
        ])

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (2,1), (-1,-1), "CENTER"),
    ]))

    story.append(table)
    doc.build(story)
    return response


@login_required
def region_subject_summary_excel(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    region, summary = get_region_subject_summary(region_id, year, subject)

    wb = Workbook()
    ws = wb.active
    ws.title = "Subject Summary"

    headers = [
        "Subject", "Name", "Total",
        "A", "B", "C", "D", "E",
        "Pass A–C", "% Pass"
    ]
    ws.append(headers)

    for r in summary:
        ws.append([
            r["subject_code"],
            r["subject_name_sw"],
            r["total_std"],
            r["A"], r["B"], r["C"], r["D"], r["E"],
            r["pass_ac"],
            r["pass_pct"],
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Subject_Summary_{region.name}_{year}.xlsx"'
    )

    wb.save(response)
    return response








@login_required
def subject_summary_pdf(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    region = get_object_or_404(Region, pk=region_id)

    # Build summary again (reuse logic)
    response_html = general_subject_summary(request).content.decode()

    response = HttpResponse(content_type="application/pdf")
    response['Content-Disposition'] = (
        f'attachment; filename="subject_summary_{region.name}_{subject}_{year}.pdf"'
    )

    pisa.CreatePDF(response_html, dest=response)
    return response


import openpyxl
from openpyxl.styles import Font, Alignment
from django.http import HttpResponse


@login_required
def subject_summary_excel(request):
    region_id = request.GET.get("region")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    region = get_object_or_404(Region, pk=region_id)

    # Fetch summary
    res = general_subject_summary(request)
    context = res.context_data
    summary = context["summary"]

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Subject Summary"

    headers = ["SUBJECT", "TOTAL", "A", "B", "C", "D", "E", "PASS A-C", "% PASS"]
    ws.append(headers)

    for cell in ws[1]:
        cell.font = Font(bold=True)
        cell.alignment = Alignment(horizontal="center")

    for row in summary:
        pass_ac = (row["A"] or 0) + (row["B"] or 0) + (row["C"] or 0)
        pct = round(pass_ac / row["total_std"] * 100, 2) if row["total_std"] else 0

        ws.append([
            row["subject_code"],
            row["total_std"],
            row["A"], row["B"], row["C"], row["D"], row["E"],
            pass_ac,
            pct
        ])

    response = HttpResponse(
        content=wb,
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="subject_summary_{subject}_{year}.xlsx"'

    wb.save(response)
    return response









@login_required
def regional_subject_school_ranking(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject")  # subject CODE

    region = get_object_or_404(Region, pk=region_id)

    # ✅ RESOLVE SUBJECT NAME
    subject_obj = ExamSubject.objects.filter(subject_id=subject).first()
    subject_name_sw = subject_obj.subject_name_sw if subject_obj else "UNKNOWN"

    schools = School.objects.filter(region_id=region_id)
    rows = []

    for s in schools:
        perf = SchoolSubjectPerformance.objects.filter(
            school_code=s.school_code,
            year=year,
            subject_code=subject
        ).first()

        if perf:
            total_std = perf.total_std or 0
            avg_subj = float(perf.subj_avg or 0)

            pass_ac = total_std if perf.subj_grade in ["A", "B", "C"] else 0
            pass_pct = round((pass_ac / total_std * 100), 2) if total_std else 0

            rows.append({
                "school_code": s.school_code,
                "school": s.name,
                "district": s.district.name if s.district else "",
                "total": total_std,
                "pass_ac": pass_ac,
                "pass_pct": pass_pct,
                "avg": round(avg_subj, 4),
            })

    rows.sort(key=lambda r: (r["avg"], r["pass_pct"]), reverse=True)

    for i, r in enumerate(rows, start=1):
        r["rank"] = i

    return render(
        request,
        "exams/reports/regional_subject_school_ranking.html",
        {
            "rows": rows,
            "region": region,
            "year": year,
            "subject_code": subject,
            "subject_name_sw": subject_name_sw,
        }
    )




 
@login_required
def regional_subject_school_ranking_pdf(request):
    ctx = get_regional_subject_school_ranking(
        request.GET.get("region"),
        int(request.GET.get("year")),
        request.GET.get("subject"),
    )

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Regional_Subject_School_Rank_{ctx["year"]}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    # -------- HEADER --------
    story.append(Paragraph(
        f"<b>REGIONAL SUBJECT SCHOOL RANKING</b><br/>"
        f"{ctx['subject_name_sw']} ({ctx['subject_code']}) – {ctx['year']}<br/>"
        f"{ctx['region'].name}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    # -------- TABLE --------
    data = [[
        "Rank", "School Code", "School",
        "District", "Total",
        "Pass A–C", "% Pass", "Average"
    ]]

    for r in ctx["rows"]:
        data.append([
            r["rank"],
            r["school_code"],
            r["school"],
            r["district"],
            r["total"],
            r["pass_ac"],
            f'{r["pass_pct"]}%',
            r["avg"],
        ])

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E2F7FD")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (4,1), (-1,-1), "CENTER"),
    ]))

    story.append(table)
    doc.build(story)
    return response





@login_required
def regional_subject_school_ranking_excel(request):
    ctx = get_regional_subject_school_ranking(
        request.GET.get("region"),
        int(request.GET.get("year")),
        request.GET.get("subject"),
    )

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Regional Subject Ranking"

    ws.append([
        "Rank", "School Code", "School",
        "District", "Total",
        "Pass A–C", "% Pass", "Average"
    ])

    for r in ctx["rows"]:
        ws.append([
            r["rank"],
            r["school_code"],
            r["school"],
            r["district"],
            r["total"],
            r["pass_ac"],
            r["pass_pct"],
            r["avg"],
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Regional_Subject_School_Rank_{ctx["year"]}.xlsx"'
    )

    wb.save(response)
    return response




















def compute_trend(region_id, start_year, end_year):

    print("DEBUG TREND INPUT:", region_id, start_year, end_year)

    start_year = int(start_year)
    end_year = int(end_year)

    years = list(range(start_year, end_year + 1))
    rows = []
    prev_pct = None

    for y in years:

        print("\n===== DEBUG YEAR:", y, "=====")

        qs = ExamCandidate.objects.filter(
            year=y,
            school__district__region_id=region_id
        )

        total = qs.filter(avg_grade__in=["A", "B", "C", "D", "E"]).count()
        passed = qs.filter(avg_grade__in=["A", "B", "C"]).count()

        pct = (passed / total * 100) if total else 0
        pct = round(pct, 3)

        # Determine remark + icon
        if prev_pct is None:
            remark = "START"
            icon = "◎"
        elif pct > prev_pct:
            remark = "RISE"
            icon = "▲"
        elif pct < prev_pct:
            remark = "DROP"
            icon = "▼"
        else:
            remark = "SAME"
            icon = "■"

        prev_pct = pct

        rows.append({
            "year": y,
            "total": total,
            "passed": passed,
            "pct": pct,
            "remark": remark,
            "icon": icon,
        })
    return rows



@login_required
def region_trend_excel(request):

    region = request.GET.get("region")
    start = request.GET.get("start")
    end = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_trend(region, start, end, exam_type_id)

    from openpyxl import Workbook
    from django.http import HttpResponse

    wb = Workbook()
    ws = wb.active
    ws.title = "Region Trend"

    ws.append([
        "Year","I","II","III","IV","0","Total",
        "Pass I-III","%","Pass I-IV","%","GPA","Trend","Remark"
    ])

    for r in rows:
        ws.append([
            r["year"], r["d1"], r["d2"], r["d3"], r["d4"], r["d0"],
            r["total"],
            r["pass_i_iii"], r["pass_i_iii_pct"],
            r["pass_i_iv"], r["pass_i_iv_pct"],
            r["gpa"], r["icon"], r["remark"]
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = "attachment; filename=Region_Trend.xlsx"

    wb.save(response)
    return response


@login_required
def region_trend_pdf(request):

    region_id = request.GET.get("region")
    start = request.GET.get("start")
    end = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_trend(region_id, start, end, exam_type_id)

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
    from reportlab.lib.pagesizes import A4
    from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
    from reportlab.lib.enums import TA_CENTER
    from reportlab.lib import colors
    from django.http import HttpResponse

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = "attachment; filename=Region_Trend_Report.pdf"

    doc = SimpleDocTemplate(response, pagesize=A4)

    styles = getSampleStyleSheet()

    title_style = ParagraphStyle(
        "TitleStyle",
        parent=styles["Heading1"],
        alignment=TA_CENTER,
        textColor=colors.black,
        fontSize=14
    )

    subtitle_style = ParagraphStyle(
        "SubtitleStyle",
        parent=styles["Normal"],
        alignment=TA_CENTER,
        fontSize=11
    )

    elements = []

    # ---------- TITLE ----------
    elements.append(Paragraph(
        f"{region.name} – {exam_type.name}",
        title_style
    ))

    elements.append(Paragraph(
        f"GPA Performance Trend ({start} – {end})",
        subtitle_style
    ))

    elements.append(Spacer(1, 12))

    # ---------- TABLE ----------
    data = [[
        "Year","I","II","III","IV","0","Total",
        "Pass I-III","%","Pass I-IV","%","GPA","Trend","Remark"
    ]]

    for r in rows:
        data.append([
            r["year"], r["d1"], r["d2"], r["d3"], r["d4"], r["d0"],
            r["total"],
            r["pass_i_iii"], r["pass_i_iii_pct"],
            r["pass_i_iv"], r["pass_i_iv_pct"],
            r["gpa"], r["icon"], r["remark"]
        ])

    table = Table(data, repeatRows=1)

    table.setStyle(TableStyle([
        ("GRID",(0,0),(-1,-1),1,colors.black),
        ("BACKGROUND",(0,0),(-1,0),colors.lightgrey),
        ("ALIGN",(0,0),(-1,-1),"CENTER"),
        ("FONT",(0,0),(-1,0),"Helvetica-Bold"),
        ("BOTTOMPADDING",(0,0),(-1,0),8),
    ]))

    elements.append(table)

    doc.build(elements)

    return response








def compute_district_trend(region_id, start_year, end_year):
    from exams.models import District, ExamCandidate

    start_year = int(start_year)
    end_year = int(end_year)

    years = list(range(start_year, end_year + 1))
    districts = District.objects.filter(region_id=region_id).order_by("name")

    result = []

    for d in districts:
        prev_pct = None
        year_rows = []

        for y in years:
            qs = ExamCandidate.objects.filter(
                year=y,
                school__district_id=d.id
            )

            total = qs.count()
            passed = qs.filter(avg_grade__in=["A", "B", "C"]).count()
            pct = round((passed / total * 100), 3) if total else 0

            # Remark + icon
            if prev_pct is None:
                remark = "START"
                icon = "◎"
            elif pct > prev_pct:
                remark = "RISE"
                icon = "▲"
            elif pct < prev_pct:
                remark = "DROP"
                icon = "▼"
            else:
                remark = "SAME"
                icon = "■"

            prev_pct = pct

            year_rows.append({
                "year": y,
                "total": total,
                "passed": passed,
                "pct": pct,
                "remark": remark,
                "icon": icon,
            })

        result.append({
            "district": d.name,
            "years": year_rows
        })

    return years, result








def district_trend_pdf(request):
    region_id = request.GET.get("region")
    start = request.GET.get("start")
    end = request.GET.get("end")

    years, rows = compute_district_trend(region_id, start, end)
    region = Region.objects.get(id=region_id)

    html = render_to_string("exams/reports/district_trend_pdf.html", {
        "region": region,
        "years": years,
        "rows": rows
    })

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = "attachment; filename=district_trend.pdf"

    pisa.CreatePDF(html, dest=response)
    return response





import openpyxl
from openpyxl.styles import Font, Alignment
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

@login_required
def district_trend_excel(request):

    district_id = request.GET.get("district")
    start = request.GET.get("start")
    end = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    # ---- build data from SINGLE source ----
    rows = build_district_trend(
        district_id=district_id,
        start_year=start,
        end_year=end,
        exam_type_id=exam_type_id
    )

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    # ---- workbook ----
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "District Trend"

    bold = Font(bold=True)
    center = Alignment(horizontal="center")

    r = 1

    # ================= HEADER =================
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=14)
    ws.cell(r, 1).value = (
        f"{exam_type} – {district.name.upper()} "
        f"GPA TREND ({start}–{end})"
    )
    ws.cell(r, 1).font = Font(bold=True, size=14)
    ws.cell(r, 1).alignment = center
    r += 2

    # ================= TABLE HEADER =================
    headers = [
        "Year",
        "I", "II", "III", "IV", "0",
        "Total",
        "Pass I–III", "% I–III",
        "Pass I–IV", "% I–IV",
        "Division GPA",
        "Subject GPA",
        "Final GPA",
        "Trend",
        "Remark",
    ]

    ws.append(headers)
    for c in range(1, len(headers) + 1):
        ws.cell(r, c).font = bold
        ws.cell(r, c).alignment = center
    r += 1

    # ================= DATA ROWS =================
    for row in rows:
        ws.append([
            row["year"],
            row["d1"],
            row["d2"],
            row["d3"],
            row["d4"],
            row["d0"],
            row["total"],
            row["pass_i_iii"],
            row["pass_i_iii_pct"],
            row["pass_i_iv"],
            row["pass_i_iv_pct"],
            row["div_gpa"],
            row["subj_gpa"],
            row["gpa"],
            row["icon"],
            row["remark"],
        ])
        r += 1

    # ================= COLUMN WIDTHS =================
    widths = {
        1: 10,  # Year
        7: 10,  # Total
        12: 12, # Div GPA
        13: 12, # Sub GPA
        14: 12, # Final GPA
        16: 12  # Remark
    }
    for col, w in widths.items():
        ws.column_dimensions[openpyxl.utils.get_column_letter(col)].width = w

    # ================= RESPONSE =================
    filename = (
        f"{exam_type}-"
        f"{start}-{end}-"
        f"{district.name.replace(' ','_')}-Trend.xlsx"
    )

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response




@login_required
def region_trend_pdf_reportlab(request):
    region_id = request.GET.get("region")
    start_year = request.GET.get("start")
    end_year = request.GET.get("end")

    if not region_id or not start_year or not end_year:
        return HttpResponse("Missing parameters", status=400)

    region = get_object_or_404(Region, pk=region_id)
    rows = compute_trend(region_id, start_year, end_year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Region_Trend_{region.name}_{start_year}_{end_year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=25, rightMargin=25,
        topMargin=20, bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    # -------- HEADER --------
    story.append(Paragraph(
        f"<b>REGION PASS TREND</b><br/>"
        f"{region.name}<br/>"
        f"{start_year} – {end_year}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    # -------- TABLE --------
    data = [[
        "Year", "Total Candidates",
        "Passed (A–C)", "% Pass",
        "Remark"
    ]]

    for r in rows:
        data.append([
            r["year"],
            r["total"],
            r["passed"],
            f'{r["pct"]}%',
            f'{r["remark"]} {r["icon"]}'
        ])

    table = Table(data, repeatRows=1, hAlign="LEFT")
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#CFEAFB")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("TOPPADDING", (0,0), (-1,-1), 6),
    ]))

    story.append(table)
    doc.build(story)

    return response







@login_required
def district_trend_pdf_reportlab(request):
    region_id = request.GET.get("region")
    start_year = request.GET.get("start")
    end_year = request.GET.get("end")

    if not region_id or not start_year or not end_year:
        return HttpResponse("Missing parameters", status=400)

    region = get_object_or_404(Region, pk=region_id)
    years, rows = compute_district_trend(region_id, start_year, end_year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Trend_{region.name}_{start_year}_{end_year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=landscape(A4),
        leftMargin=20, rightMargin=20,
        topMargin=20, bottomMargin=20
    )

    styles = getSampleStyleSheet()
    story = []

    # -------- HEADER --------
    story.append(Paragraph(
        f"<b>DISTRICT PASS TREND</b><br/>"
        f"{region.name}<br/>"
        f"{start_year} – {end_year}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    # -------- TABLE HEADER --------
    header = ["District"]
    for y in years:
        header += [
            f"{y} Total",
            f"{y} Pass",
            f"{y} %",
            f"{y} Remark"
        ]

    data = [header]

    # -------- ROWS --------
    for d in rows:
        row = [d["district"]]
        for yr in d["years"]:
            row += [
                yr["total"],
                yr["passed"],
                f'{yr["pct"]}%',
                f'{yr["remark"]} {yr["icon"]}'
            ]
        data.append(row)

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E2F7FD")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
        ("FONTSIZE", (0,0), (-1,-1), 8),
        ("BOTTOMPADDING", (0,0), (-1,-1), 4),
        ("TOPPADDING", (0,0), (-1,-1), 4),
    ]))

    story.append(table)
    doc.build(story)

    return response



















# --------------------------------------------------------------
# MAIN DASHBOARD API (based on SchoolYearSummary)
# --------------------------------------------------------------
@require_GET
@login_required
def ajax_dashboard_data(request):

    region = request.GET.get("region")
    district = request.GET.get("district")
    ward = request.GET.get("ward")
    school = request.GET.get("school")
    examtype = request.GET.get("examtype")
    ownership = request.GET.get("ownership")
    start_year = request.GET.get("start_year")
    end_year = request.GET.get("end_year")
    subject_param = request.GET.get("subject")
    gender = request.GET.get("gender")
 
    # =======================
    # School summary queryset
    # =======================
    qs = SchoolYearSummary.objects.select_related("school", "exam_type")

    if region:
        qs = qs.filter(school__region_id=region)
    if district:
        qs = qs.filter(school__district_id=district)
    if ward:
        qs = qs.filter(school__ward_id=ward)
    if school:
        qs = qs.filter(school__school_code=school)
    if examtype:
        qs = qs.filter(exam_type_id=examtype)
    if ownership:
        qs = qs.filter(school__ownership=ownership)
    if start_year:
        qs = qs.filter(year__gte=start_year)
    if end_year:
        qs = qs.filter(year__lte=end_year)

    profile = getattr(request.user, "profile", None)
    if profile and profile.role != "super":
        if profile.role == "region":
            qs = qs.filter(school__region_id=profile.region_id)
        elif profile.role == "district":
            qs = qs.filter(school__district_id=profile.district_id)
        elif profile.role == "ward":
            qs = qs.filter(school__ward_id=profile.ward_id)
        elif profile.role == "school":
            qs = qs.filter(school__school_code=profile.school.school_code)

    # =====================================
    # Division summary queryset (IMPORTANT)
    # =====================================
    div_qs = SchoolDivisionSummary.objects.select_related("school")

    if region:
        div_qs = div_qs.filter(school__region_id=region)
    if district:
        div_qs = div_qs.filter(school__district_id=district)
    if ward:
        div_qs = div_qs.filter(school__ward_id=ward)
    if school:
        div_qs = div_qs.filter(school__school_code=school)
    if examtype:
        div_qs = div_qs.filter(exam_type_id=examtype)
    if ownership:
        div_qs = div_qs.filter(school__ownership=ownership)
    if start_year:
        div_qs = div_qs.filter(year__gte=start_year)
    if end_year:
        div_qs = div_qs.filter(year__lte=end_year)
    if gender in ["M", "F"]:
        div_qs = div_qs.filter(sex=gender)

    if profile and profile.role != "super":
        if profile.role == "region":
            div_qs = div_qs.filter(school__region_id=profile.region_id)
        elif profile.role == "district":
            div_qs = div_qs.filter(school__district_id=profile.district_id)
        elif profile.role == "ward":
            div_qs = div_qs.filter(school__ward_id=profile.ward_id)
        elif profile.role == "school":
            div_qs = div_qs.filter(school__school_code=profile.school.school_code)

    # =====================
    # PASS / FAIL KPIs
    # =====================
    PASS_DIVS = ["I", "II", "III", "IV"]

    FAIL_DIVS = ["DIV 0","DIV O","0","F"]

    fail_candidates = div_qs.filter(
        division__in=FAIL_DIVS
    ).aggregate(t=Sum("total"))["t"] or 0

    pass_candidates = div_qs.filter(division__in=PASS_DIVS).aggregate(t=Sum("total"))["t"] or 0
    # fail_candidates = div_qs.filter(division="O").aggregate(t=Sum("total"))["t"] or 0

    total_candidates = pass_candidates + fail_candidates

    pass_pct = round((pass_candidates / max(total_candidates,1)) * 100, 1)

    donut = [
        {"name": "Pass", "value": int(pass_candidates)},
        {"name": "Fail", "value": int(fail_candidates)},
    ]


    # =====================
    # PASS / FAIL KPIs
    # =====================
    PASS_I_III_DIVS = ["I", "II", "III"]

    pass_i_iii_candidates = div_qs.filter(
        division__in=PASS_I_III_DIVS
    ).aggregate(t=Sum("total"))["t"] or 0

    total_candidates = pass_candidates + fail_candidates

    pass_i_iii_pct = round((pass_i_iii_candidates / max(total_candidates,1)) * 100, 1)


    # =====================
    # Gender KPIs
    # =====================
    male_total = div_qs.filter(sex="M").aggregate(t=Sum("total"))["t"] or 0
    female_total = div_qs.filter(sex="F").aggregate(t=Sum("total"))["t"] or 0

    male_pass = div_qs.filter(sex="M", division__in=PASS_DIVS).aggregate(t=Sum("total"))["t"] or 0
    female_pass = div_qs.filter(sex="F", division__in=PASS_DIVS).aggregate(t=Sum("total"))["t"] or 0

    male_pct = round((male_pass / male_total * 100),1) if male_total else 0
    female_pct = round((female_pass / female_total * 100),1) if female_total else 0

    male_i_iii_pass = div_qs.filter(sex="M", division__in=PASS_I_III_DIVS).aggregate(t=Sum("total"))["t"] or 0
    female_i_iii_pass = div_qs.filter(sex="F", division__in=PASS_I_III_DIVS).aggregate(t=Sum("total"))["t"] or 0

    male_i_iii_pct = round((male_i_iii_pass / male_total * 100),1) if male_total else 0
    female_i_iii_pct = round((female_i_iii_pass / female_total * 100),1) if female_total else 0


    # =====================
    # Grades distribution
    # =====================
    grade_order = ["I","II","III","IV","0"]
    grade_counts_raw = div_qs.values("division").annotate(t=Sum("total"))

    grade_values = [
        next((g["t"] for g in grade_counts_raw if g["division"] == gr),0)
        for gr in grade_order
    ]

    # =====================
    # Gender pass per year
    # =====================
    gender_stats = (
        div_qs.values("year")
        .annotate(
            male_pass=Sum("total", filter=Q(sex="M", division__in=PASS_DIVS)),
            male_total=Sum("total", filter=Q(sex="M")),
            female_pass=Sum("total", filter=Q(sex="F", division__in=PASS_DIVS)),
            female_total=Sum("total", filter=Q(sex="F")),
        )
        .order_by("year")
    )

    gender_years, male_rates, female_rates = [], [], []

    for r in gender_stats:
        gender_years.append(r["year"])
        m = (r["male_pass"] or 0) / (r["male_total"] or 1) * 100
        f = (r["female_pass"] or 0) / (r["female_total"] or 1) * 100
        male_rates.append(round(m,1))
        female_rates.append(round(f,1))

    # =====================
    # Line chart (GPA per year – NECTA)
    # =====================
    from exams.gpa_calculation import (
        region_division_gpa,
        region_subject_gpa_value,
        district_division_gpa,
        district_subject_gpa_value,
    )

    year_list = (
        qs.values_list("year", flat=True)
        .distinct()
        .order_by("year")
    )

    years = []
    avg_series = []

    for y in year_list:

        # -------------------
        # REGION LEVEL
        # -------------------
        if region and not district and not ward and not school:
            div_gpa = region_division_gpa(
                year=y,
                exam_type_id=examtype,
                region_id=region,
            )

            subj_gpa = region_subject_gpa_value(
                year=y,
                exam_type_id=examtype,
                region_id=region,
            )

            final_gpa = (div_gpa + subj_gpa) / 2 if div_gpa and subj_gpa else 0

        # -------------------
        # DISTRICT LEVEL
        # -------------------
        elif district and not ward and not school:
            div_gpa = district_division_gpa(
                year=y,
                exam_type_id=examtype,
                district_id=district,
            )

            subj_gpa = district_subject_gpa_value(
                year=y,
                exam_type_id=examtype,
                district_id=district,
            )

            final_gpa = (div_gpa + subj_gpa) / 2 if div_gpa and subj_gpa else 0

        # -------------------
        # SCHOOL LEVEL
        # -------------------
        elif school:
            sy = SchoolYearSummary.objects.filter(
                year=y,
                exam_type_id=examtype,
                school__school_code=school
            ).first()

            final_gpa = float(sy.avg_gpa) if sy and sy.avg_gpa else 0

        # -------------------
        # FALLBACK (mixed filters)
        # -------------------
        else:
            final_gpa = qs.filter(year=y).aggregate(
                gpa=Avg("avg_gpa")
            )["gpa"] or 0

        years.append(y)
        avg_series.append(round(float(final_gpa), 4))






    # =====================
    # Line chart (school avg)
    # =====================
    # year_stats = qs.values("year").annotate(avg_mark=Avg("avg_numeric")).order_by("year")
    # years = [r["year"] for r in year_stats]
    # avg_series = [float(r["avg_mark"] or 0) for r in year_stats]

    # =====================
    # Top schools
    # =====================
    top_schools_raw = qs.values("school__school_code","school__name")\
        .annotate(avg_mark=Avg("avg_numeric"), candidates=Sum("candidates"))\
        .order_by("-avg_mark")[:10]

    top_schools = [
        {
            "school_code": r["school__school_code"],
            "school": r["school__name"],
            "avg": round(float(r["avg_mark"] or 0),2),
            "candidates": int(r["candidates"] or 0),
        }
        for r in top_schools_raw
    ]

    # =====================
    # Final JSON
    # =====================
    data = {
        "cards": [
            {"title":"Total Schools","value":qs.values("school__school_code").distinct().count()},
            {"title":"Total Candidates","value":int(total_candidates)},
            {"title":"Avg Marks","value":round(qs.aggregate(avg=Avg("avg_numeric"))["avg"] or 0,2)},
            {"title":"Pass %","value":f"{pass_pct}%"},
        ],
        "donut":donut,
        "line":{"years":years,"avg_marks":avg_series},
        "top_schools":top_schools,
        "grades":{"labels":grade_order,"values":grade_values},
        "gender_pass":{"years":gender_years,"male":male_rates,"female":female_rates},
        "gender_kpi":{
            "pass_i_iii": pass_i_iii_candidates,
            "pass_i_iii_pct": pass_i_iii_pct,

            "male_total":male_total,
            "female_total":female_total,
            "male_pass":male_pass,
            "female_pass":female_pass,
            "male_pct":male_pct,
            "female_pct":female_pct,

            "male_i_iii_pass":male_i_iii_pass,
            "female_i_iii_pass":female_i_iii_pass,
            "male_i_iii_pct":male_i_iii_pct,
            "female_i_iii_pct":female_i_iii_pct,
        }
    }

    return JsonResponse(data, safe=False)










@login_required
def bulk_uploads_page(request):
    stats = {
        "school_year_summary": SchoolYearSummary.objects.count(),
        "school_division_summary": SchoolDivisionSummary.objects.count(),
        "school_subject_grade_summary": SchoolSubjectGradeSummary.objects.count(),
        "exam_candidate": ExamCandidate.objects.count(),
        "school_subject_performance": SchoolSubjectPerformance.objects.count(),
        "student_subject_mark": StudentSubjectMark.objects.count(),
        "auth_user": UserProfile.objects.count(),
    }

    return render(request, "exams/bulk_uploads.html", {
        "stats": stats,
        "exam_types": ExamType.objects.all()
    })


TABLES = {
    "exam_candidate": {
        "table": "exam_candidate",
        "columns": "(year, school_code, cand_no, prem_no, name, sex, avg_numeric, avg_grade, full_results, division, points, cand_comb)",
        "requires_exam_type": True,
    },

    "student_subject_mark": {
        "table": "student_subject_mark",
        "columns": "(year, school_code, cand_no, subject_code, grade, numeric_score)",
        "requires_exam_type": True,
    },
    "school_year_summary": {
        "table": "school_year_summary",
        "columns": "(year, candidates, avg_numeric, avg_grade, status, school_code, avg_gpa)",
        "requires_exam_type": True,
    },
    "school_subject_performance": {
        "table": "school_subject_performance",
        "columns": "(school_code, year, subject_code, total_std, subj_avg, subj_grade, subj_status,subj_gpa, total_pass)",
        "requires_exam_type": True,
    },


    "school_division_summary": {
        "table": "school_division_summary",
        "columns": "(year, sex, division, total, school_code)",
        "requires_exam_type": True,
    },

    "school_subject_grade_summary": {
        "table": "school_subject_grade_summary",
        "columns": "(year, subject_code, sex, grade, total, school_code)",
        "requires_exam_type": True,
    },
    "auth_user": {
        "table": "auth_user",
        "columns": "(password, username, email)",
    },

}




@login_required
def bulk_upload_handler(request, target):
    if request.method != "POST":
        return redirect("exams:bulk_uploads")

    csv_file = request.FILES.get("csv_file")
    exam_type_id = request.POST.get("exam_type_id")

    if not csv_file:
        messages.error(request, "No CSV file selected.")
        return redirect("exams:bulk_uploads")

    if target not in TABLES:
        messages.error(request, "Invalid upload target.")
        return redirect("exams:bulk_uploads")

    cfg = TABLES[target]

    # Validate exam_type if required
    if cfg["requires_exam_type"]:
        if not exam_type_id or not ExamType.objects.filter(id=exam_type_id).exists():
            messages.error(request, "Valid Exam Type is required for this upload.")
            return redirect("exams:bulk_uploads")

    # Save temp file
    with tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode="wb") as tmp:
        for chunk in csv_file.chunks():
            tmp.write(chunk)
        file_path = tmp.name.replace("\\", "/")

    try:
        from django.db import connection

        with connection.cursor() as cursor:
            cursor.execute("SET GLOBAL local_infile = 1")

            set_clause = ""
            if cfg["requires_exam_type"]:
                set_clause = f" SET exam_type_id = {int(exam_type_id)}"

            sql = f"""
                LOAD DATA LOCAL INFILE '{file_path}'
                INTO TABLE {cfg['table']}
                FIELDS TERMINATED BY ','
                OPTIONALLY ENCLOSED BY '"'
                LINES TERMINATED BY '\\r\\n'
                IGNORE 1 LINES
                {cfg['columns']}
                {set_clause}
            """

            cursor.execute(sql)
            rows = cursor.rowcount
            connection.commit()

        messages.success(
            request,
            f"{target.replace('_',' ').title()} uploaded successfully. {rows} rows inserted."
        )

    except Exception as e:
        messages.error(request, f"Upload failed: {e}")

    finally:
        if os.path.exists(file_path):
            os.remove(file_path)

    return redirect("exams:bulk_uploads")











@login_required
@require_POST
def bulk_upload_delete(request, target):

    TABLE_MAP = {
        "exam_candidate": "exam_candidate",
        "school_year_summary": "school_year_summary",
        "student_subject_mark": "student_subject_mark",
        "school_subject_performance": "school_subject_performance",

        "school_division_summary": "school_division_summary",
        "school_subject_grade_summary": "school_subject_grade_summary", 

    }

    if target not in TABLE_MAP:
        messages.error(request, "Invalid delete target.")
        return redirect("exams:bulk_uploads")

    table = TABLE_MAP[target]

    with connection.cursor() as cursor:
        cursor.execute(f"TRUNCATE TABLE {table}")

    messages.success(
        request,
        f"All records deleted from {target.upper()}."
    )

    return redirect("exams:bulk_uploads")



@login_required
def bulk_upload_sample(request, target):

    SAMPLES = {
        "exam_candidate": [
            ["year", "school_code", "cand_no", "prem_no","name", "sex", "avg_grade", "status", "full_results", "division", "points", "cand_comb"]
        ],
        "student_subject_mark": [
            ["year","school_code","cand_no","subject_code","grade","numeric_score"]
        ],
        "school_year_summary": [
            ["year","candidates","avg_numeric","avg_grade","status","school_code","avg_gpa"]
        ],
        "school_subject_performance": [
            ["school_code","year","subject_code","total_std","subj_avg","subj_grade","subj_status","subj_gpa","total_pass"]
        ],

        "school_division_summary": [
            ["year","sex","division","total","school_code"]
        ],

        "school_subject_grade_summary": [
            ["year","subject_code","sex","grade","total","school_code"]
        ],
    }

    if target not in SAMPLES:
        return HttpResponse("Invalid sample", status=400)

    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = f"attachment; filename={target}_sample.csv"

    writer = csv.writer(response)
    for row in SAMPLES[target]:
        writer.writerow(row)

    return response






@login_required
def district_subject_rank_pdf(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject")

    ctx = get_district_subject_ranking(region_id, year, subject)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Subject_Rank_{ctx["region"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    story.append(Paragraph(
        f"<b>DISTRICT SUBJECT RANKING – {ctx['subject_name_sw']} ({year})</b><br/>{ctx['region'].name}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    data = [[
        "Rank", "District",
        "Total Std", "Pass A–C", "% Pass", "Average"
    ]]

    for r in ctx["rows"]:
        data.append([
            r["rank"],
            r["district"],
            r["total"],
            r["pass_ac"],
            f'{r["pass_pct"]}%',
            r["avg"],
        ])

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (2,1), (-1,-1), "CENTER"),
    ]))

    story.append(table)
    doc.build(story)
    return response






@login_required
def district_subject_rank_excel(request):
    region_id = request.GET.get("region")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject")

    ctx = get_district_subject_ranking(region_id, year, subject)

    wb = Workbook()
    ws = wb.active
    ws.title = "District Subject Rank"

    ws.append([
        "Rank", "District",
        "Total Std", "Pass I–IV", "% Pass", "Average"
    ])

    for r in ctx["rows"]:
        ws.append([
            r["rank"],
            r["district"],
            r["total"],
            r["pass_ac"],
            r["pass_pct"],
            r["avg"],
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="District_Subject_Rank_{ctx["region"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response









# views.py
@login_required
def districtwise(request):

    user = request.user
    years = ["2022", "2023", "2024", "2025"]

    # ---------------- ROLE FILTERING ----------------
    if user.profile.role == "super":
        districts = District.objects.select_related("region").all().order_by("name")

    elif user.profile.role == "region":
        districts = District.objects.filter(
            region_id=user.profile.region_id
        ).select_related("region").order_by("name")

    elif user.profile.role == "district":
        districts = District.objects.filter(
            id=user.profile.district_id
        ).select_related("region").order_by("name")

    elif user.profile.role == "ward":
        districts = District.objects.filter(
            region_id=user.profile.ward.district.region_id
        ).select_related("region").order_by("name")

    elif user.profile.role == "school":
        districts = District.objects.filter(
            region_id=user.profile.school.region_id
        ).select_related("region").order_by("name")

    else:
        districts = District.objects.none()

    subjects = ExamSubject.objects.all().order_by("subject_code")

    # ✅ LOAD EXAM TYPES (ID + NAME)
    exam_types = ExamType.objects.filter(is_active=True).only("id", "name").order_by("-year")

    # ---------------- AJAX SEARCH ----------------
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        query = request.GET.get("q", "").strip().lower()

        results = []
        for d in districts:
            if query in d.name.lower():
                results.append({
                    "id": d.id,
                    "name": d.name,
                    "region": d.region.name,
                    "ward_count": d.wards.count(),
                })

        return JsonResponse({
            "districts": results,
            "exam_types": [
                {"id": e.id, "name": e.name} for e in exam_types
            ]
        })

    # ---------------- NORMAL PAGE LOAD ----------------
    return render(
        request,
        "exams/reports/districtwise.html",
        {
            "districts": districts,
            "subjects": subjects,
            "years": years,
            "exam_types": exam_types,   # ✅ now available in template
        }
    )




@login_required
def district_summary_html(request):
    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    profile = getattr(request.user, "profile", None)

    if profile and profile.role != "super":
        if profile.role == "district":
            if int(district_id) != profile.district_id:
                return HttpResponseForbidden("Access denied")

        elif profile.role == "region":
            if not District.objects.filter(
                id=district_id,
                region=profile.region
            ).exists():
                return HttpResponseForbidden("Access denied")

        else:
            return HttpResponseForbidden("Access denied")

    ctx = build_district_summary_context(
        district_id, year, exam_type_id
    )
    return render(request, "exams/reports/district_summary_html.html", ctx)



@login_required
def district_trend_html(request):

    district_id = request.GET.get("district")
    start = request.GET.get("start")
    end = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = build_district_trend(
        district_id=district_id,
        start_year=start,
        end_year=end,
        exam_type_id=exam_type_id
    )

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    return render(
        request,
        "exams/reports/district_trend_html.html",
        {
            "district": district,
            "exam_type": exam_type,
            "exam_type_id": exam_type_id,
            "start": start,
            "end": end,
            "rows": rows,
        }
    )



@login_required
def district_ward_ranking_html(request):
    rows = build_district_ward_ranking_context(
        request.GET["district"],
        request.GET["year"],
        request.GET["exam_type_id"]
    )

    return render(request,"exams/reports/district_ward_rank_html.html",{
        "rows":rows,
        "district":District.objects.get(id=request.GET["district"]),
        "year":request.GET["year"]
    })






# views.py
from exams.views_helpers import build_district_summary_context
from exams.app_utils.pdf.pdf_tables import pdf_district_summary_block

from reportlab.platypus import SimpleDocTemplate, Paragraph, Table, TableStyle, Spacer
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import cm   # ← REQUIRED

@login_required
def district_summary_pdf(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    ctx = build_district_summary_context(district_id, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Summary_{ctx["district"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=1.2 * cm,
        rightMargin=1.2 * cm,
        topMargin=1.5 * cm,
        bottomMargin=1.5 * cm,
    )

    styles = getSampleStyleSheet()
    story = []

    # =========================================================
    # HEADER
    # =========================================================
    story.append(Paragraph(
        "<b>OFISI YA RAIS – TAMISEMI</b><br/>"
        f"<b>MATOKEO YA DARASA LA NNE – {year}</b><br/><br/>"
        f"<b>WILAYA:</b> {ctx['district'].name}",
        styles["Title"]
    ))
    story.append(Spacer(1, 10))

    # =========================================================
    # SUMMARY (WANAFUNZI)
    # =========================================================
    t = ctx["totals"]

    summary_data = [
        ["", "WASICHANA", "%", "WAVULANA", "%", "JUMLA", "%"],
        [
            "WALIOSAJILIWA",
            t["reg_f"], pct(t["reg_f"], t["reg"]),
            t["reg_m"], pct(t["reg_m"], t["reg"]),
            t["reg"], "100",
        ],
        [
            "WENYE MATOKEO",
            t["sat_f"], pct(t["sat_f"], t["reg"]),
            t["sat_m"], pct(t["sat_m"], t["reg"]),
            t["sat"], pct(t["sat"], t["reg"]),
        ],
        [
            "WASIOFANYA",
            t["abs_f"], pct(t["abs_f"], t["reg"]),
            t["abs_m"], pct(t["abs_m"], t["reg"]),
            t["abs"], pct(t["abs"], t["reg"]),
        ],
    ]

    table = Table(summary_data, colWidths=[5*cm, 2*cm, 2*cm, 2*cm, 2*cm, 2*cm, 2*cm])
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightblue),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
    ]))
    story.append(table)
    story.append(Spacer(1, 12))

    # =========================================================
    # GRADE DISTRIBUTION (A–E)
    # =========================================================
    g = ctx["grades"]

    grade_data = [
        ["DARAJA", "A", "B", "C", "D", "E", "PASS A–C", "%"],
        [
            "JUMLA",
            g["a"], g["b"], g["c"], g["d"], g["e"],
            ctx["a_c_total"], pct(ctx["a_c_total"], t["sat"]),
        ],
    ]

    grade_table = Table(grade_data, colWidths=[4*cm]*8)
    grade_table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
    ]))
    story.append(grade_table)
    story.append(Spacer(1, 14))

    # =========================================================
    # SCHOOL RANKING
    # =========================================================
    story.append(Paragraph("<b>UPANGAJI WA SHULE (WASTANI)</b>", styles["Heading2"]))
    story.append(Spacer(1, 6))

    school_data = [
        ["NA", "SHULE", "WASTANI", "DARAJA", "HALI"]
    ]

    for s in ctx["schools"]:
        school_data.append([
            s["rank"],
            s["name"],
            s["avg"],
            s["grade"],
            s["status"],
        ])

    school_table = Table(
        school_data,
        colWidths=[1.2*cm, 7*cm, 2.5*cm, 2.5*cm, 3*cm],
        repeatRows=1
    )
    school_table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E2F7FD")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (0,1), (-1,-1), "CENTER"),
        ("ALIGN", (1,1), (1,-1), "LEFT"),
    ]))

    story.append(school_table)

    doc.build(story)
    return response




import openpyxl
from openpyxl.styles import Font, Alignment
from django.http import HttpResponse

@login_required
def district_summary_excel(request):

    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    ctx = build_district_summary_context(
        district_id=district_id,
        year=year,
        exam_type_id=exam_type_id
    )

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "District Summary"

    bold = Font(bold=True)
    center = Alignment(horizontal="center")

    r = 1

    # ================= HEADER =================
    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=6)
    ws.cell(r, 1).value = (
        f"{ctx['exam_type_obj']} - "
        f"{ctx['district'].name.upper()} - {year}"
    )
    ws.cell(r, 1).font = Font(bold=True, size=14)
    ws.cell(r, 1).alignment = center
    r += 2

    # ================= TOTALS =================
    ws.cell(r,1).value = "SUMMARY"
    ws.cell(r,1).font = bold
    r += 1

    totals = ctx["totals"]
    rows = [
        ("Registered", totals["reg"]),
        ("Sat", totals["sat"]),
        ("Absent", totals["abs"]),
        ("GPA", totals["avg"]),
        ("Grade", totals["grade"]),
        ("Status", totals["status"]),
    ]

    for k,v in rows:
        ws.cell(r,1).value = k
        ws.cell(r,2).value = v
        r += 1

    r += 1

    # ================= DIVISION SUMMARY =================
    ws.cell(r,1).value = "DIVISION SUMMARY"
    ws.cell(r,1).font = bold
    r += 1

    ws.append(["Division","Female","Male","Total"])
    for c in range(1,5):
        ws.cell(r,c).font = bold
    r += 1

    g = ctx["grades"]
    divs = [
        ("I", g["af"], g["am"], g["a"]),
        ("II", g["bf"], g["bm"], g["b"]),
        ("III", g["cf"], g["cm"], g["c"]),
        ("IV", g["df"], g["dm"], g["d"]),
        ("0", g["ef"], g["em"], g["e"]),
    ]

    for d in divs:
        ws.append(d)
        r += 1

    r += 1

    # ================= WARD PERFORMANCE =================
    if "ward_perf" in ctx and ctx["ward_perf"]:
        ws.cell(r,1).value = "WARD PERFORMANCE"
        ws.cell(r,1).font = bold
        r += 1

        ws.append(["Rank","Ward","GPA","Grade","Status"])
        for c in range(1,6):
            ws.cell(r,c).font = bold
        r += 1

        for w in ctx["ward_perf"]:
            ws.append([
                w["no"],
                w["name"],
                w["gpa"],
                w["grade"],
                w["status"]
            ])
            r += 1

        r += 1

    # ================= BEST SCHOOLS =================
    if "best_schools" in ctx and ctx["best_schools"]:
        ws.cell(r,1).value = "BEST 10 SCHOOLS"
        ws.cell(r,1).font = bold
        r += 1

        ws.append([
            "#","School","Sat",
            "I","II","III","IV","0",
            "Pass","%","GPA","Grade","Status"
        ])
        for c in range(1,14):
            ws.cell(r,c).font = bold
        r += 1

        for s in ctx["best_schools"]:
            ws.append([
                s["no"],
                s["name"],
                s["sat"],
                s["at"],
                s["bt"],
                s["ct"],
                s["dt"],
                s["ft"],
                s["pass_no"],
                s["pass_pct"],
                s["gpa"],
                s["grade"],
                s["status"],
            ])
            r += 1

    # ================= RESPONSE =================
    filename = (
        f"{ctx['exam_type_obj']}-"
        f"{year}-{ctx['district'].name.replace(' ','_')}-Summary.xlsx"
    )

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response




@login_required
def district_school_ranking_html(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank(district, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/district_school_rank_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "district": Region.objects.get(id=district)
    })





@login_required
def district_ward_ranking_pdf(request):
    ctx = build_district_ward_ranking_context(
        request.GET.get("district"),
        int(request.GET.get("year"))
    )

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = "inline; filename=ward_ranking.pdf"

    doc = SimpleDocTemplate(response)
    story = []

    story.append(Paragraph(
        f"<b>WARD RANKING – {ctx['district'].name} ({ctx['year']})</b>",
        getSampleStyleSheet()["Title"]
    ))

    rows = [
        [r["rank"], r["ward"], r["total"], r["pass"], f"{r['pass_pct']}%", r["avg"]]
        for r in ctx["rows"]
    ]

    story.append(simple_rank_table(
        ["#", "Ward", "Total", "Pass", "% Pass", "Avg"], rows
    ))

    doc.build(story)
    return response
 

@login_required
def district_school_ranking_excel(request):
    ctx = build_district_school_ranking_context(
        request.GET.get("district"),
        int(request.GET.get("year"))
    )

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "School Ranking"

    ws.append(["#", "School", "Ward", "Ownership", "Total", "Pass", "% Pass", "Average"])

    for r in ctx["rows"]:
        ws.append([
            r["rank"], r["school"], r["ward"], r["ownership"],
            r["total"], r["pass"], r["pass_pct"], r["avg"]
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = "attachment; filename=school_ranking.xlsx"
    wb.save(response)
    return response




@login_required
def district_ward_ranking_excel(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    ctx = build_district_ward_ranking_context(district_id, year)

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Ward Ranking"

    bold = Font(bold=True)

    # ================= HEADER =================
    ws.append([f"WARD RANKING – {ctx['district'].name} – {year}"])
    ws["A1"].font = Font(bold=True, size=14)
    ws.append([])

    # ================= TABLE HEADER =================
    headers = [
        "Rank",
        "Ward",
        "Total Candidates",
        "Pass A–C",
        "% Pass",
        "Average Score",
    ]
    ws.append(headers)

    for col in range(1, len(headers) + 1):
        ws.cell(row=ws.max_row, column=col).font = bold

    # ================= DATA ROWS =================
    for r in ctx["rows"]:
        ws.append([
            r["rank"],
            r["ward"],
            r["total"],
            r["pass"],
            r["pass_pct"],
            r["avg"],
        ])

    # ================= AUTO WIDTH =================
    for col in ws.columns:
        max_length = 0
        col_letter = col[0].column_letter
        for cell in col:
            if cell.value:
                max_length = max(max_length, len(str(cell.value)))
        ws.column_dimensions[col_letter].width = max_length + 3

    # ================= RESPONSE =================
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="District_Ward_Ranking_{ctx["district"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def district_school_ranking_pdf(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    ctx = build_district_summary_context(district_id, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_School_Ranking_{ctx["district"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    # ================= HEADER =================
    story.append(Paragraph(
        f"<b>DISTRICT SCHOOL RANKING</b><br/>"
        f"{ctx['district'].name} – {year}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    # ================= TABLE DATA =================
    data = [[
        "Rank",
        "School",
        "Ownership",
        "Total",
        "Pass A–C",
        "% Pass",
        "Average"
    ]]

    for s in ctx["schools"]:
        pass_pct = round((s["pass"] / s["total"] * 100), 2) if s["total"] else 0

        data.append([
            s["rank"],
            s["name"],
            s["ownership"],
            s["total"],
            s["pass"],
            f"{pass_pct}%",
            s["avg"],
        ])

    # ================= TABLE =================
    table = Table(data, repeatRows=1, colWidths=[
        40, 140, 90, 60, 60, 60, 60
    ])

    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E8F1FA")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (3,1), (-1,-1), "CENTER"),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ]))

    story.append(table)

    doc.build(story)
    return response


@login_required
def district_top_bottom_pdf(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    ctx = build_district_top_bottom_context(district_id, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Top_Bottom_{ctx["district"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    story.append(Paragraph(
        f"<b>DISTRICT TOP / BOTTOM SCHOOLS</b><br/>"
        f"{ctx['district'].name} – {year}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    def table_block(title, rows):
        data = [[
            "Rank", "School", "District",
            "Total", "Pass A–C", "% Pass",
            "Average", "Grade", "Status"
        ]]
        for r in rows:
            data.append([
                r["rank"], r["name"], r["district"],
                r["total"], r["pass_ac"],
                f'{r["pass_pct"]}%',
                r["avg"], r["avg_grade"], r["status"]
            ])

        t = Table(data, repeatRows=1)
        t.setStyle(TableStyle([
            ("GRID", (0,0), (-1,-1), 0.5, colors.black),
            ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E2F7FD")),
            ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
            ("ALIGN", (3,1), (-1,-1), "CENTER"),
        ]))

        return [
            Paragraph(f"<b>{title}</b>", styles["Heading3"]),
            Spacer(1, 6),
            t,
            Spacer(1, 12),
        ]

    story += table_block("TOP 10 SCHOOLS", ctx["top10"])
    story += table_block("BOTTOM 10 SCHOOLS", ctx["bottom10"])
    story += table_block("TOP 10 LARGE SCHOOLS (≥30)", ctx["top10_big"])
    story += table_block("BOTTOM 10 LARGE SCHOOLS (≥30)", ctx["bottom10_big"])
    story += table_block("TOP 10 GOVERNMENT SCHOOLS", ctx["top10_gov"])

    doc.build(story)
    return response


@login_required
def district_top_bottom_excel(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    ctx = build_district_top_bottom_context(district_id, year)

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Top Bottom Schools"

    bold = Font(bold=True)

    ws.append([f"DISTRICT TOP / BOTTOM SCHOOLS – {ctx['district'].name} – {year}"])
    ws["A1"].font = Font(bold=True, size=14)
    ws.append([])

    headers = [
        "Rank", "School", "District",
        "Total", "Pass A–C", "% Pass",
        "Average", "Grade", "Status"
    ]

    def write_block(title, rows):
        ws.append([title])
        ws[ws.max_row][0].font = bold
        ws.append(headers)
        for r in rows:
            ws.append([
                r["rank"], r["name"], r["district"],
                r["total"], r["pass_ac"],
                r["pass_pct"], r["avg"],
                r["avg_grade"], r["status"]
            ])
        ws.append([])

    write_block("TOP 10 SCHOOLS", ctx["top10"])
    write_block("BOTTOM 10 SCHOOLS", ctx["bottom10"])
    write_block("TOP 10 LARGE SCHOOLS (≥30)", ctx["top10_big"])
    write_block("BOTTOM 10 LARGE SCHOOLS (≥30)", ctx["bottom10_big"])
    write_block("TOP 10 GOVERNMENT SCHOOLS", ctx["top10_gov"])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="District_Top_Bottom_{ctx["district"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response







@login_required
def district_top_bottom_html(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))

    district = get_object_or_404(District, pk=district_id)

    # --------------------------------------------------
    # Get ALL schools in this district for the year
    # --------------------------------------------------
    summaries = SchoolYearSummary.objects.filter(
        year=year,
        school__district=district
    ).select_related("school", "school__district")

    rows = []

    for s in summaries:
        school = s.school

        # Candidate stats per school
        cand_qs = ExamCandidate.objects.filter(
            year=year,
            school=school
        )

        def g(grade):
            return cand_qs.filter(avg_grade__iexact=grade).count()

        A = g("A")
        B = g("B")
        C = g("C")
        D = g("D")
        E = g("E")

        total = cand_qs.exclude(avg_grade="A-ABS").count()
        pass_ac = A + B + C
        pass_pct = round((pass_ac / total * 100), 2) if total else 0

        rows.append({
            "school_code": school.school_code,
            "name": school.name,
            "district": district.name,
            "total": total,
            "A": A, "B": B, "C": C, "D": D, "E": E,
            "pass_ac": pass_ac,
            "pass_pct": pass_pct,
            "avg": round(float(s.avg_numeric or 0), 4),
            "avg_grade": s.avg_grade or "-",
            "status": s.status or "-",
            "ownership": (school.ownership or "").upper(),
        })

    # --------------------------------------------------
    # BUILD TOP / BOTTOM TABLES
    # --------------------------------------------------
    top10 = sorted(rows, key=lambda x: x["avg"], reverse=True)[:10]
    bottom10 = sorted(rows, key=lambda x: x["avg"])[:10]

    large = [r for r in rows if r["total"] >= 30]
    top10_big = sorted(large, key=lambda x: x["avg"], reverse=True)[:10]
    bottom10_big = sorted(large, key=lambda x: x["avg"])[:10]

    gov = [r for r in rows if r["ownership"] == "GVT"]
    top10_gov = sorted(gov, key=lambda x: x["avg"], reverse=True)[:10]

    # --------------------------------------------------
    # Assign ranks (IMPORTANT for HTML/PDF/Excel)
    # --------------------------------------------------
    def add_rank(data):
        for i, r in enumerate(data, start=1):
            r["rank"] = i
        return data

    context = {
        "district": district,
        "year": year,
        "top10": add_rank(top10),
        "bottom10": add_rank(bottom10),
        "top10_big": add_rank(top10_big),
        "bottom10_big": add_rank(bottom10_big),
        "top10_gov": add_rank(top10_gov),
    }

    return render(
        request,
        "exams/reports/district_top_bottom.html",
        context
    )






@login_required
def district_subject_summary(request):
    ctx = build_district_subject_summary(
        request.GET.get("district"),
        int(request.GET.get("year")),
        request.GET.get("subject", "ALL"),
    )
    return render(request, "exams/reports/district_subject_summary.html", ctx)













from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required

@login_required
def district_subject_school_ranking(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject")  # subject CODE

    district = get_object_or_404(District, pk=district_id)

    # ✅ RESOLVE SUBJECT NAME (SAME PATTERN AS REGION)
    subject_obj = ExamSubject.objects.filter(subject_id=subject).first()
    subject_name_sw = subject_obj.subject_name_sw if subject_obj else "UNKNOWN"

    # ✅ ONLY SCHOOLS IN THIS DISTRICT
    schools = School.objects.filter(district=district)

    rows = []

    for s in schools:
        # ✅ USE SchoolSubjectPerformance (FAST, NO JOIN ISSUES)
        perf = SchoolSubjectPerformance.objects.filter(
            school_code=s.school_code,
            year=year,
            subject_code=subject
        ).first()

        if not perf:
            continue

        total_std = perf.total_std or 0
        avg_subj = float(perf.subj_avg or 0)

        # PASS A–C BASED ON SUBJECT GRADE
        pass_ac = total_std if perf.subj_grade in ["A", "B", "C"] else 0
        pass_pct = round((pass_ac / total_std * 100), 2) if total_std else 0

        rows.append({
            "school_code": s.school_code,
            "school": s.name,
            "ward": s.ward.name if s.ward else "",
            "ownership": s.ownership,
            "total": total_std,
            "pass_ac": pass_ac,
            "pass_pct": pass_pct,
            "avg": round(avg_subj, 4),
        })

    # ✅ SAME SORT LOGIC AS REGION
    rows.sort(key=lambda r: (r["avg"], r["pass_pct"]), reverse=True)

    # ✅ ASSIGN RANK
    for i, r in enumerate(rows, start=1):
        r["rank"] = i

    return render(
        request,
        "exams/reports/district_subject_rank.html",
        {
            "rows": rows,
            "district": district,
            "year": year,
            "subject_code": subject,
            "subject_name_sw": subject_name_sw,
        }
    )

@login_required
def district_subject_summary_pdf(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    ctx = build_district_subject_summary(district_id, year, subject)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Subject_Summary_{ctx["district"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=25,
        rightMargin=25,
        topMargin=25,
        bottomMargin=30
    )

    styles = getSampleStyleSheet()
    story = []

    # ---------- HEADER ----------
    title = (
        f"<b>DISTRICT SUBJECT SUMMARY</b><br/>"
        f"{ctx['district'].name} – {year}<br/>"
        f"SUBJECT: {ctx['subject']}"
    )
    story.append(Paragraph(title, styles["Title"]))
    story.append(Spacer(1, 12))

    # ---------- TABLE ----------
    table_data = [[
        "Code", "Subject",
        "Total",
        "A", "B", "C", "D", "E",
        "Pass A–C", "% Pass",
        "Average"
    ]]

    for r in ctx["rows"]:
        table_data.append([
            r["subject_code"],
            r["subject_name_sw"],
            r["total_std"],
            r["A"], r["B"], r["C"], r["D"], r["E"],
            r["pass_ac"],
            f'{r["pass_pct"]}%',
            r["avg_subj"],
        ])

    table = Table(table_data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.HexColor("#E2F7FD")),
        ("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (2,1), (-1,-1), "CENTER"),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
    ]))

    story.append(table)
    doc.build(story)

    return response


@login_required
def district_subject_summary_excel(request):
    district_id = request.GET.get("district")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    ctx = build_district_subject_summary(district_id, year, subject)

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "District Subject Summary"

    bold = Font(bold=True)

    # ---------- HEADER ----------
    ws.append([f"DISTRICT SUBJECT SUMMARY – {ctx['district'].name} – {year}"])
    ws["A1"].font = Font(bold=True, size=14)
    ws.append([f"SUBJECT: {ctx['subject']}"])
    ws.append([])

    # ---------- TABLE HEADER ----------
    headers = [
        "Code", "Subject",
        "Total",
        "A", "B", "C", "D", "E",
        "Pass A–C", "% Pass",
        "Average"
    ]
    ws.append(headers)
    for col in range(1, len(headers) + 1):
        ws.cell(row=ws.max_row, column=col).font = bold

    # ---------- DATA ----------
    for r in ctx["rows"]:
        ws.append([
            r["subject_code"],
            r["subject_name_sw"],
            r["total_std"],
            r["A"], r["B"], r["C"], r["D"], r["E"],
            r["pass_ac"],
            r["pass_pct"],
            r["avg_subj"],
        ])

    # ---------- AUTO WIDTH ----------
    for col in ws.columns:
        max_len = 0
        col_letter = col[0].column_letter
        for cell in col:
            if cell.value:
                max_len = max(max_len, len(str(cell.value)))
        ws.column_dimensions[col_letter].width = max_len + 3

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="District_Subject_Summary_{ctx["district"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def district_d_trend_html(request):
    district_id = request.GET.get("district")

    ctx = build_district_trend(district_id)

    return render(
        request,
        "exams/reports/district_d_trend_html.html",
        ctx
    )




@login_required
def district_d_trend_pdf(request):
    district_id = request.GET.get("district")
    ctx = build_district_trend(district_id)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="District_Trend_{ctx["district"].name}.pdf"'
    )

    doc = SimpleDocTemplate(response, pagesize=A4)
    styles = getSampleStyleSheet()
    story = []

    story.append(Paragraph(
        f"<b>DISTRICT PERFORMANCE TREND</b><br/>{ctx['district'].name}",
        styles["Title"]
    ))
    story.append(Spacer(1, 12))

    data = [["Year", "Average", "Grade", "Total Candidates"]]

    for r in ctx["rows"]:
        data.append([r["year"], r["avg"], r["grade"], r["total"]])

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
    ]))

    story.append(table)
    doc.build(story)

    return response




@login_required
def district_d_trend_excel(request):
    district_id = request.GET.get("district")
    ctx = build_district_trend(district_id)

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "District Trend"

    ws.append(["Year", "Average", "Grade", "Total Candidates"])

    for r in ctx["rows"]:
        ws.append([r["year"], r["avg"], r["grade"], r["total"]])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="District_Trend_{ctx["district"].name}.xlsx"'
    )

    wb.save(response)
    return response
















@login_required
def wardwise(request):
    user = request.user
    profile = user.profile
    years = ["2022", "2023", "2024", "2025"]

    # ----------------------------------
    # ROLE-BASED VISIBILITY
    # ----------------------------------
    if profile.role == "super":
        wards = Ward.objects.select_related("district", "district__region")

    elif profile.role == "region":
        wards = Ward.objects.filter(
            district__region=profile.region
        ).select_related("district", "district__region")

    elif profile.role == "district":
        wards = Ward.objects.filter(
            district=profile.district
        ).select_related("district", "district__region")

    elif profile.role == "ward":
        wards = Ward.objects.filter(
            id=profile.ward_id
        ).select_related("district", "district__region")

    elif profile.role == "school":
        wards = Ward.objects.filter(
            id=profile.school.ward_id
        ).select_related("district", "district__region")

    else:
        wards = Ward.objects.none()

    # ----------------------------------
    # AJAX SEARCH (same as regionwise)
    # ----------------------------------
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        query = request.GET.get("q", "").strip().lower()

        results = []
        for w in wards:
            if query in w.name.lower():
                results.append({
                    "id": w.id,
                    "name": w.name,
                    "district": w.district.name,
                    "region": w.district.region.name,
                })

        return JsonResponse({"wards": results})

    return render(
        request,
        "exams/reports/wardwise.html",
        {
            "wards": wards,
            "years": years,
        }
    )



@login_required
def ward_summary_html(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")

    if not ward_id or not year:
        return HttpResponse("Missing ward or year", status=400)

    context = build_ward_summary_context(ward_id, year)

    return render(
        request,
        "exams/reports/ward_summary_html.html",
        context
    )


@login_required
def ward_summary_pdf(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")

    if not ward_id or not year:
        return HttpResponse("Missing parameters", status=400)

    context = build_ward_summary_context(ward_id, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Ward_{context["ward"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=30
    )

    story = []

    # Header
    story.append(
        pdf_header_block_simple(context["ward"], year)
    )
    story.append(Spacer(1, 8))

    # Students
    story.append(pdf_students_block(context["totals"]))
    story.append(Spacer(1, 10))

    # Grades
    story.append(
        pdf_grades_block(context["grades"], context["totals"])
    )
    story.append(Spacer(1, 12))

    # Ownership
    for tbl in pdf_ownership_block(context["ownership"]):
        story.append(tbl)
        story.append(Spacer(1, 12))

    # Schools summary
    if context.get("schools"):
        story.append(
            pdf_district_summary_block(
                context["schools"],
                context["schools_totals"]
            )
        )

    doc.build(story, onFirstPage=_on_page, onLaterPages=_on_page)
    return response


@login_required
def ward_summary_excel(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")

    if not ward_id or not year:
        return HttpResponse("Missing parameters", status=400)

    context = build_ward_summary_context(ward_id, year)

    wb = Workbook()
    ws = wb.active
    ws.title = "Ward Summary"

    bold = Font(bold=True)
    head = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")

    # Title
    ws.merge_cells("A1:G1")
    ws["A1"] = f"WARD SUMMARY – {context['ward'].name} – {year}"
    ws["A1"].font = Font(bold=True, size=14)
    ws["A1"].alignment = Alignment(horizontal="center")

    ws.append([])

    # Registered / Sat / Absent
    ws.append(["Category", "Female", "Female %", "Male", "Male %", "Total", "Total %"])
    for c in ws[3]:
        c.font = bold
        c.fill = head

    t = context["totals"]

    def pct(a, b): return round((a / b * 100), 1) if b else 0

    ws.append(["Registered", t["reg_f"], pct(t["reg_f"], t["reg"]),
               t["reg_m"], pct(t["reg_m"], t["reg"]), t["reg"], 100])

    ws.append(["With Results", t["sat_f"], pct(t["sat_f"], t["reg"]),
               t["sat_m"], pct(t["sat_m"], t["reg"]), t["sat"], pct(t["sat"], t["reg"])])

    ws.append(["Absent", t["abs_f"], pct(t["abs_f"], t["reg"]),
               t["abs_m"], pct(t["abs_m"], t["reg"]), t["abs"], pct(t["abs"], t["reg"])])

    ws.append(["Average Score", t["avg"], "", "", "", "", ""])
    ws.append(["Grade", t["grade"], "", "", "", "", ""])
    ws.append(["Status", t["status"], "", "", "", "", ""])
    ws.append(["Total Schools", t["school_count"], "", "", "", "", ""])

    ws.append([])

    # Schools summary
    ws.append(["SCHOOL SUMMARY"])
    ws["A12"].font = bold

    ws.append(["School", "Total", "Pass", "Fail", "Pass %", "Male", "Female"])
    for c in ws[13]:
        c.font = bold
        c.fill = head

    for s in context["schools"]:
        ws.append([
            s["school_name"],
            s["total"],
            s["pass"],
            s["fail"],
            s["pass_pct"],
            s["male"],
            s["female"],
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Ward_{context["ward"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def ward_school_ranking_html(request):
    ward_id = request.GET.get("ward")
    year = int(request.GET.get("year", 2024))

    ward = get_object_or_404(Ward, pk=ward_id)

    rows, totals = build_school_rank_data(
        region_id=None,   # ✅ DO NOT FORCE REGION
        year=year,
        ward_id=ward.id
    )

    return render(
        request,
        "exams/reports/ward_school_ranking_html.html",
        {
            "ward": ward,
            "year": year,
            "rows": rows,
            "totals": totals,
        }
    )





@login_required
def ward_school_ranking_pdf(request):
    ward_id = request.GET.get("ward")
    year = int(request.GET.get("year", 2024))

    ward = get_object_or_404(Ward, pk=ward_id)

    rows, totals = build_school_rank_data(
        ward.district.region_id,
        year,
        ward_id=ward.id
    )

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Ward_School_Ranking_{ward.name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=landscape(A4),
        leftMargin=20,
        rightMargin=20,
        topMargin=20,
        bottomMargin=20
    )

    story = []

    # Header
    story.append(
        pdf_header_block_simple(ward, year)
    )
    story.append(Spacer(1, 10))

    # Ranking Table
    story.append(
        pdf_school_ranking_block(rows, totals)
    )

    doc.build(story)
    return response


@login_required
def ward_school_ranking_excel(request):
    ward_id = request.GET.get("ward")
    year = int(request.GET.get("year", 2024))

    ward = get_object_or_404(Ward, pk=ward_id)

    rows, totals = build_school_rank_data(
        ward.district.region_id,
        year,
        ward_id=ward.id
    )

    wb = Workbook()
    ws = wb.active
    ws.title = "Ward School Ranking"

    bold = Font(bold=True)

    ws.append([
        "Rank", "School Code", "School Name",
        "A", "B", "C", "D", "E",
        "Pass A–C", "% Pass", "Average", "Total"
    ])

    for c in ws[1]:
        c.font = bold

    for r in rows:
        ws.append([
            r["rank"],
            r["school_code"],
            r["name"],
            r["a"],
            r["b"],
            r["c"],
            r["d"],
            r["e"],
            r["pass_t"],
            r["pass_pct"],
            r["avg_score"],
            r["total_std"],
        ])

    # TOTAL ROW
    ws.append([
        "TOTAL", "", "",
        totals["a"],
        totals["b"],
        totals["c"],
        totals["d"],
        totals["e"],
        totals["pass_t"],
        "",
        totals["avg_score"],
        totals["total_std"],
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Ward_School_Ranking_{ward.name}_{year}.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def ward_subject_summary_html(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject", "ALL")

    context = build_ward_subject_summary(ward_id, year, subject)

    return render(
        request,
        "exams/reports/ward_subject_summary_html.html",
        context
    )


@login_required
def ward_subject_summary_pdf(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject", "ALL")

    context = build_ward_subject_summary(ward_id, year, subject)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Ward_Subject_Summary_{context["ward"].name}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(response, pagesize=A4)
    story = []

    story.append(pdf_header_block_simple(context["ward"], year))
    story.append(Spacer(1, 10))
    story.append(pdf_subject_summary_block(context["rows"], context["totals"]))

    doc.build(story)
    return response

@login_required
def ward_subject_summary_excel(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject", "ALL")

    context = build_ward_subject_summary(ward_id, year, subject)

    wb = Workbook()
    ws = wb.active
    ws.title = "Ward Subject Summary"

    bold = Font(bold=True)

    ws.append([
        "Subject Code", "Subject Name",
        "Total", "Pass A–C", "% Pass", "Fail", "Average"
    ])

    for c in ws[1]:
        c.font = bold

    for r in context["rows"]:
        ws.append([
            r["subject_code"],
            r["subject_name"],
            r["total"],
            r["pass_ac"],
            r["pass_pct"],
            r["fail"],
            r["avg"],
        ])

    ws.append([
        "TOTAL", "",
        context["totals"]["total"],
        context["totals"]["pass_ac"],
        "",
        context["totals"]["fail"],
        context["totals"]["avg"],
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Ward_Subject_Summary_{context["ward"].name}_{year}.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def ward_subject_school_ranking_html(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    context = build_ward_subject_school_ranking(
        ward_id=ward_id,
        year=year,
        subject_code=subject
    )

    subject_obj = ExamSubject.objects.get(subject_code=subject)
    context["subject_name"] = subject_obj.subject_name_sw

    return render(
        request,
        "exams/reports/ward_subject_school_ranking.html",
        context
    )

@login_required
def ward_subject_school_ranking_pdf(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    context = build_ward_subject_school_ranking(
        ward_id=ward_id,
        year=year,
        subject_code=subject
    )

    subject_obj = ExamSubject.objects.get(subject_code=subject)
    context["subject_name"] = subject_obj.subject_name_sw

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="Ward_Subject_Ranking_{subject}_{year}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=landscape(A4),
        leftMargin=20,
        rightMargin=20
    )

    story = []
    story.append(pdf_header_block_simple(context["ward"], year))
    story.append(Spacer(1, 10))

    story.append(
        pdf_subject_school_ranking_block(
            context["rows"],
            context["subject_code"],
            context["subject_name"]
        )
    )

    doc.build(story)
    return response


@login_required
def ward_subject_school_ranking_excel(request):
    ward_id = request.GET.get("ward")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    context = build_ward_subject_school_ranking(
        ward_id=ward_id,
        year=year,
        subject_code=subject
    )

    subject_obj = ExamSubject.objects.get(subject_code=subject)
    subject_name = subject_obj.subject_name_sw

    wb = Workbook()
    ws = wb.active
    ws.title = "Ward Subject Ranking"

    bold = Font(bold=True)

    ws.append([
        "Rank", "School Code", "School",
        "District", "Total",
        "Pass A–C", "% Pass", "Average"
    ])

    for c in ws[1]:
        c.font = bold

    for r in context["rows"]:
        ws.append([
            r["rank"],
            r["school_code"],
            r["school"],
            r["district"],
            r["total"],
            r["pass_ac"],
            r["pass_pct"],
            r["avg"],
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="Ward_Subject_Ranking_{subject}_{year}.xlsx"'
    )

    wb.save(response)
    return response







@login_required
def schoolwise(request):
    user = request.user
    profile = user.profile

    years = ["2022", "2023", "2024", "2025"]

    qs = School.objects.select_related(
        "ward",
        "district",
        "district__region"
    )

    # ---------------- ROLE FILTERING ----------------
    if profile.role == "super":
        pass

    elif profile.role == "region":
        qs = qs.filter(district__region=profile.region)

    elif profile.role == "district":
        qs = qs.filter(district=profile.district)

    elif profile.role == "ward":
        qs = qs.filter(ward=profile.ward)

    elif profile.role == "school":
        qs = qs.filter(school_code=profile.school.school_code)

    else:
        qs = School.objects.none()

    # ---------------- LOAD EXAM TYPES ----------------
    exam_types = ExamType.objects.filter(is_active=True).only("id", "name")

    # ---------------- AJAX SEARCH ----------------
    if request.headers.get("x-requested-with") == "XMLHttpRequest":
        q = request.GET.get("q", "").lower()
        data = []

        for s in qs:
            if q in s.name.lower():
                data.append({
                    "id": s.school_code,
                    "name": s.name,
                    "ward": s.ward.name,
                    "district": s.district.name,
                    "region": s.district.region.name,
                })

        return JsonResponse({
            "schools": data,
            "exam_types": [
                {"id": e.id, "name": e.name} for e in exam_types
            ]
        })

    # ---------------- NORMAL PAGE LOAD ----------------
    return render(
        request,
        "exams/reports/schoolwise.html",
        {
            "schools": qs,
            "years": years,
            "subjects": ExamSubject.objects.all(),
            "exam_types": exam_types,   # ✅ NOW AVAILABLE IN TEMPLATE
        }
    )





@login_required
def school_summary_html(request):
    school_code = request.GET.get("school")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    profile = getattr(request.user, "profile", None)

    # 🔒 BLOCK UNAUTHORIZED ACCESS
    if profile and profile.role != "super":
        if profile.role == "school":
            if school_code != profile.school.school_code:
                return HttpResponseForbidden("Access denied")

        elif profile.role == "ward":
            if not School.objects.filter(
                school_code=school_code,
                ward=profile.ward
            ).exists():
                return HttpResponseForbidden("Access denied")

        elif profile.role == "district":
            if not School.objects.filter(
                school_code=school_code,
                district=profile.district
            ).exists():
                return HttpResponseForbidden("Access denied")

        elif profile.role == "region":
            if not School.objects.filter(
                school_code=school_code,
                region=profile.region
            ).exists():
                return HttpResponseForbidden("Access denied")

    # ✅ SAFE TO PROCEED
    ctx = build_school_summary_context(
        school_code, year, exam_type_id
    )
    return render(request, "exams/reports/school_summary_html.html", ctx)







from django.http import JsonResponse
from exams.models import SchoolYearSummary

@login_required
def check_school_summary_exists(request):
    school = request.GET.get("school")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    exists = SchoolYearSummary.objects.filter(
        school__school_code=school,
        year=year,
        exam_type_id=exam_type_id
    ).exists()

    return JsonResponse({"exists": exists})


from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

from reportlab.platypus import (
    SimpleDocTemplate, Table, TableStyle,
    Paragraph, Spacer, PageBreak
)
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib import colors

from exams.views_helpers import build_school_summary_context


@login_required
def school_summary_pdf(request):
    school_code = request.GET.get("school")
    year = request.GET.get("year")

    context = build_school_summary_context(school_code, year)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename=School_{school_code}_{year}.pdf'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        rightMargin=20,
        leftMargin=20,
        topMargin=20,
        bottomMargin=20
    )

    styles = getSampleStyleSheet()
    styles.add(ParagraphStyle(
        name="Header",
        alignment=1,
        fontSize=14,
        spaceAfter=8,
        fontName="Helvetica-Bold"
    ))
    styles.add(ParagraphStyle(
        name="SubHeader",
        alignment=1,
        fontSize=11,
        spaceAfter=6
    ))
    styles.add(ParagraphStyle(
        name="Bold",
        fontName="Helvetica-Bold"
    ))

    story = []

    school = context["school"]
    totals = context["totals"]
    grades = context["grades"]

    # =====================================================
    # HEADER
    # =====================================================
    story.append(Paragraph("OFISI YA RAIS - TAMISEMI", styles["Header"]))
    story.append(Paragraph(
        f"MATOKEO YA DARASA LA NNE - {context['year']}",
        styles["Header"]
    ))
    story.append(Spacer(1, 10))

    story.append(Paragraph(
        f"<b>SHULE:</b> {school.name}", styles["Normal"]
    ))
    story.append(Paragraph(
        f"<b>KATA:</b> {school.ward.name} &nbsp;&nbsp; "
        f"<b>WILAYA:</b> {school.district.name}",
        styles["Normal"]
    ))
    story.append(Paragraph(
        f"<b>MKOA:</b> {school.district.region.name}",
        styles["Normal"]
    ))
    story.append(Spacer(1, 12))

    # =====================================================
    # WANAFUNZI TABLE
    # =====================================================
    student_table = Table([
        ["WANAFUNZI", "", "", "", "", ""],
        ["", "WASICHANA", "", "WAVULANA", "", "JUMLA"],
        ["", "NA", "%", "NA", "%", "NA"],

        ["Waliosajiliwa",
         totals["reg_f"], "100",
         totals["reg_m"], "100",
         totals["reg"]],

        ["Wenye Matokeo",
         totals["sat_f"], "",
         totals["sat_m"], "",
         totals["sat"]],

        ["Wasiofanya",
         totals["abs_f"], "",
         totals["abs_m"], "",
         totals["abs"]],
    ], colWidths=[90, 50, 40, 50, 40, 50])

    student_table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("SPAN", (0,0), (-1,0)),
        ("BACKGROUND", (0,0), (-1,0), colors.lightblue),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
        ("FONT", (0,0), (-1,0), "Helvetica-Bold"),
        ("FONT", (0,1), (-1,2), "Helvetica-Bold"),
    ]))

    story.append(student_table)
    story.append(Spacer(1, 12))

    # =====================================================
    # GRADES TABLE
    # =====================================================
    grade_table = Table([
        ["UFAULU WA MADARAJA", "", "", "", "", "", ""],
        ["", "A", "B", "C", "D", "E", "PASS A–C"],

        ["Wasichana",
         grades["af"], grades["bf"], grades["cf"],
         grades["df"], grades["ef"],
         context["a_c_f"]],

        ["Wavulana",
         grades["am"], grades["bm"], grades["cm"],
         grades["dm"], grades["em"],
         context["a_c_m"]],

        ["Jumla",
         grades["a"], grades["b"], grades["c"],
         grades["d"], grades["e"],
         context["a_c_total"]],
    ], colWidths=[90, 45, 45, 45, 45, 45, 60])

    grade_table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("SPAN", (0,0), (-1,0)),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("FONT", (0,0), (-1,0), "Helvetica-Bold"),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
    ]))

    story.append(grade_table)
    story.append(Spacer(1, 12))

    # =====================================================
    # AVERAGE & STATUS
    # =====================================================
    avg_table = Table([
        ["WASTANI WA SHULE", context["totals"]["avg"]],
        ["HALI", context["totals"]["status"]],
    ], colWidths=[120, 80])

    avg_table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("FONT", (0,0), (0,-1), "Helvetica-Bold"),
        ("ALIGN", (1,0), (-1,-1), "CENTER"),
    ]))

    story.append(avg_table)

    # =====================================================
    # BUILD PDF
    # =====================================================
    doc.build(story)

    return response



from django.http import HttpResponse
from django.contrib.auth.decorators import login_required

from openpyxl import Workbook
from openpyxl.styles import Font, Alignment
from openpyxl.utils import get_column_letter

from exams.views_helpers import build_school_summary_context


@login_required
def school_summary_excel(request):

    school_code  = request.GET.get("school")
    year         = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    if not all([school_code, year, exam_type_id]):
        return HttpResponse("Missing parameters", status=400)

    context = build_school_summary_context(
        school_code,
        year,
        exam_type_id
    )

    school = context["school"]
    totals = context["totals"]
    grades = context["grades"]
    subject_rows = context.get("subjects", [])

    wb = Workbook()
    ws = wb.active
    ws.title = "School Summary"

    bold = Font(bold=True)
    center = Alignment(horizontal="center")

    def row(data, bold_cols=None):
        ws.append(data)
        r = ws.max_row
        for c in range(1, len(data) + 1):
            ws.cell(row=r, column=c).alignment = center
        if bold_cols:
            for c in bold_cols:
                ws.cell(row=r, column=c).font = bold

    # --------------------------------------------------
    # HEADER
    # --------------------------------------------------
    ws.merge_cells("A1:J1")
    ws["A1"] = "PRIME MINISTER'S OFFICE -RALG"
    ws["A1"].font = bold
    ws["A1"].alignment = center

    ws.merge_cells("A2:J2")
    ws["A2"] = f"SCHOOL SUMMARY REPORT - {year}"
    ws["A2"].font = bold
    ws["A2"].alignment = center

    ws.append([])

    row(["SCHOOL", school.name], [1])
    row(["DISTRICT", school.district.name], [1])
    row(["REGION", school.district.region.name], [1])
    row(["EXAM TYPE", context.get("exam_type")], [1])

    ws.append([])

    # --------------------------------------------------
    # STUDENT SUMMARY
    # --------------------------------------------------
    row(["STUDENTS REGISTRATION AND PERFORMANCE"], [1])
    row(["", "FEMALE NO", "%", "MALE NO", "%", "TOTAL"])

    row([
        "REGISTERED",
        totals["reg_f"], 100,
        totals["reg_m"], 100,
        totals["reg"]
    ])

    row([
        "WITH RESULTS",
        totals["sat_f"], round(totals["sat_f"] / max(totals["reg"], 1) * 100, 1),
        totals["sat_m"], round(totals["sat_m"] / max(totals["reg"], 1) * 100, 1),
        totals["sat"]
    ])

    row([
        "ABSENT",
        totals["abs_f"], round(totals["abs_f"] / max(totals["reg"], 1) * 100, 1),
        totals["abs_m"], round(totals["abs_m"] / max(totals["reg"], 1) * 100, 1),
        totals["abs"]
    ])

    ws.append([])

    row(["AVERAGE GPA", totals["avg"]])
    row(["STATUS", totals["status"]])

    ws.append([])

    # --------------------------------------------------
    # DIVISION PERFORMANCE
    # --------------------------------------------------
    row(["DIVISION PERFORMANCE"], [1])
    row(["SEX", "I", "II", "III", "IV", "0", "PASS I-III", "PASS I-IV"])

    row([
        "F",
        grades["af"], grades["bf"], grades["cf"],
        grades["df"], grades["ef"],
        context["a_c_f"], context["a_d_f"]
    ])

    row([
        "M",
        grades["am"], grades["bm"], grades["cm"],
        grades["dm"], grades["em"],
        context["a_c_m"], context["a_d_m"]
    ])

    row([
        "T",
        grades["a"], grades["b"], grades["c"],
        grades["d"], grades["e"],
        context["a_c_total"], context["a_d_total"]
    ])

    ws.append([])
    ws.append([])

    # --------------------------------------------------
    # SUBJECT PERFORMANCE
    # --------------------------------------------------
    row(["SUBJECT PERFORMANCE"], [1])

    row([
        "CODE","SUBJECT","SAT",
        "A F","A M","A T",
        "B F","B M","B T",
        "C F","C M","C T",
        "D F","D M","D T",
        "F F","F M","F T",
        "PASS NO","PASS %",
        "GPA","GRADE","STATUS"
    ])

    for s in subject_rows:
        row([
            s["code"],
            s["subject"],
            s["sat"],
            s["af"], s["am"], s["at"],
            s["bf"], s["bm"], s["bt"],
            s["cf"], s["cm"], s["ct"],
            s["df"], s["dm"], s["dt"],
            s["ff"], s["fm"], s["ft"],
            s["pass_no"], s["pass_pct"],
            s["gpa"], s["grade"], s["status"]
        ])

    for col in range(1, 20):
        ws.column_dimensions[get_column_letter(col)].width = 16

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename=School_{school_code}_{year}_Exam{exam_type_id}_Summary.xlsx'
    )

    wb.save(response)
    return response




@login_required
def school_subject_summary_html(request):
    school_code = request.GET.get("school")
    year = int(request.GET.get("year"))
    subject = request.GET.get("subject", "ALL")

    context = build_school_subject_summary_context(
        school_code=school_code,
        year=year,
        subject=subject
    )

    return render(
        request,
        "exams/reports/school_subject_summary_html.html",
        context
    )


from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required

from exams.models import School
from exams.views_helpers import build_school_trend_context


@login_required
def school_trend_html(request):

    school_code  = request.GET.get("school")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    if not school_code or not start_year or not end_year or not exam_type_id:
        return HttpResponse("Missing parameters", status=400)

    rows = compute_school_trend(
        school_code,
        start_year,
        end_year,
        exam_type_id
    )

    school = School.objects.get(school_code=school_code)
    exam_type = ExamType.objects.get(id=exam_type_id)

    return render(request, "exams/reports/school_trend_html.html", {
        "school": school,
        "rows": rows,
        "start": int(start_year),
        "end": int(end_year),
        "exam_type_id": int(exam_type_id),
        "exam_type": exam_type.name,   # ✅ THIS LINE FIXES IT
        "years": list(range(int(start_year), int(end_year) + 1))
    })





from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors

@login_required
def school_subject_summary_pdf(request):
    school_code = request.GET.get("school")
    year = request.GET.get("year")
    subject = request.GET.get("subject", "ALL")

    context = build_school_subject_summary_context(
        school_code, year, subject
    )

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename=School_Subject_{school_code}_{year}.pdf'
    )

    doc = SimpleDocTemplate(response, pagesize=A4)
    styles = getSampleStyleSheet()
    story = []

    school = context["school"]

    story.append(Paragraph("OFISI YA RAIS - TAMISEMI", styles["Title"]))
    story.append(Paragraph(
        f"{school.name} – {context['subject_name']} ({year})",
        styles["Heading2"]
    ))
    story.append(Spacer(1, 10))

    data = [
        ["Subject", "Total", "Pass (A–C)", "Fail (D–E)", "Pass %", "Average"]
    ]

    for r in context["rows"]:
        data.append([
            r["subject"], r["total"], r["pass"],
            r["fail"], r["pass_pct"], r["avg"]
        ])

    table = Table(data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("ALIGN", (1,1), (-1,-1), "CENTER"),
        ("FONT", (0,0), (-1,0), "Helvetica-Bold"),
    ]))

    story.append(table)
    doc.build(story)

    return response



from openpyxl import Workbook
from openpyxl.styles import Font, Alignment

@login_required
def school_subject_summary_excel(request):
    school_code = request.GET.get("school")
    year = request.GET.get("year")
    subject = request.GET.get("subject", "ALL")

    context = build_school_subject_summary_context(
        school_code, year, subject
    )

    wb = Workbook()
    ws = wb.active
    ws.title = "Subject Summary"

    bold = Font(bold=True)
    center = Alignment(horizontal="center")

    ws.append(["School", context["school"].name])
    ws.append(["Year", year])
    ws.append(["Subject", context["subject_name"]])
    ws.append([])

    ws.append([
        "Subject", "Total", "Pass (A–C)",
        "Fail (D–E)", "Pass %", "Average"
    ])

    for cell in ws[5]:
        cell.font = bold
        cell.alignment = center

    for r in context["rows"]:
        ws.append([
            r["subject"], r["total"], r["pass"],
            r["fail"], r["pass_pct"], r["avg"]
        ])

    for row in ws.iter_rows(min_row=6):
        for cell in row:
            cell.alignment = center

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename=School_Subject_{school_code}_{year}.xlsx'
    )

    wb.save(response)
    return response












from exams.views_helpers import build_school_trend_context

@login_required
def school_trend_pdf(request):
    school_code = request.GET.get("school")
    start = int(request.GET.get("start"))
    end = int(request.GET.get("end"))

    context = build_school_trend_context(
        school_code=school_code,
        start=start,
        end=end
    )

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="School_Trend_{school_code}_{start}_{end}.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=A4,
        rightMargin=30,
        leftMargin=30,
        topMargin=30,
        bottomMargin=30,
    )

    styles = getSampleStyleSheet()
    elements = []

    # -----------------------------
    # TITLE (USE EXISTING STYLE)
    # -----------------------------
    elements.append(
        Paragraph(
            f"School Performance Trend ({start} – {end})",
            styles["Title"]
        )
    )
    elements.append(Spacer(1, 10))

    school = context["school"]
    elements.append(
        Paragraph(
            f"<b>{school.name}</b><br/>"
            f"Ward: {school.ward.name} | "
            f"District: {school.district.name} | "
            f"Region: {school.district.region.name}",
            styles["Normal"]
        )
    )

    elements.append(Spacer(1, 15))

    # -----------------------------
    # TABLE
    # -----------------------------
    table_data = [
        ["Year", "Total Students", "Pass (A–C)", "Fail (D–E)", "Pass %", "Average"]
    ]

    for r in context["rows"]:
        table_data.append([
            r["year"],
            r["total_std"],
            r["pass_ac"],
            r["fail_de"],
            f'{r["pass_pct"]}%',
            r["avg"],
        ])

    table = Table(table_data, repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID", (0, 0), (-1, -1), 0.5, colors.black),
        ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
        ("ALIGN", (1, 1), (-1, -1), "CENTER"),
        ("FONT", (0, 0), (-1, 0), "Helvetica-Bold"),
    ]))

    elements.append(table)

    doc.build(elements)
    return response










from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment

from exams.views_helpers import build_school_trend_context


@login_required
def school_trend_excel(request):
    school_code = request.GET.get("school")
    start = int(request.GET.get("start"))
    end = int(request.GET.get("end"))

    context = build_school_trend_context(
        school_code=school_code,
        start=start,
        end=end
    )

    school = context["school"]
    rows = context["rows"]

    wb = Workbook()
    ws = wb.active
    ws.title = "School Trend"

    bold = Font(bold=True)
    center = Alignment(horizontal="center")

    # ---------------- HEADER ----------------
    ws.append([
        "Year",
        "Total Students",
        "Pass (A–C)",
        "Fail (D–E)",
        "Pass %",
        "Average Score",
    ])

    for cell in ws[1]:
        cell.font = bold
        cell.alignment = center

    # ---------------- DATA ----------------
    for r in rows:
        ws.append([
            r["year"],
            r["total_std"],
            r["pass_ac"],
            r["fail_de"],
            r["pass_pct"],
            r["avg"],
        ])

    # ---------------- AUTOSIZE ----------------
    for col in ws.columns:
        max_len = max(len(str(cell.value)) if cell.value else 0 for cell in col)
        ws.column_dimensions[col[0].column_letter].width = max_len + 4

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="School_Trend_{school.school_code}_{start}_{end}.xlsx"'
    )

    wb.save(response)
    return response


































# --------------------------------------------------------------
# MAIN DASHBOARD API (based on SchoolYearSummary)
# --------------------------------------------------------------

from django.http import JsonResponse
from django.views.decorators.http import require_GET
from django.contrib.auth.decorators import login_required
from django.db.models import Avg, Sum

from exams.models import (
    School,
    ExamCandidate,
    StudentSubjectMark,
    SchoolSubjectPerformance,
)


@login_required
@require_GET
def ajax_dashboard_subjectwise(request):

    region     = request.GET.get("region")
    district   = request.GET.get("district")
    ward       = request.GET.get("ward")
    school     = request.GET.get("school")
    examtype   = request.GET.get("examtype")
    subject    = request.GET.get("subject")
    gender     = request.GET.get("gender")
    ownership  = request.GET.get("ownership")
    start_year = request.GET.get("start_year")
    end_year   = request.GET.get("end_year")

    # =====================================================
    # 1. SCHOOL SCOPE
    # =====================================================
    school_qs = School.objects.all()

    if region:
        school_qs = school_qs.filter(region_id=region)
    if district:
        school_qs = school_qs.filter(district_id=district)
    if ward:
        school_qs = school_qs.filter(ward_id=ward)
    if ownership:
        school_qs = school_qs.filter(ownership=ownership)
    if school:
        school_qs = school_qs.filter(school_code=school)

    school_codes = list(school_qs.values_list("school_code", flat=True))

    # =====================================================
    # 2. SUBJECT PERFORMANCE TABLE
    # =====================================================


    perf_qs = SchoolSubjectPerformance.objects.filter(
        school_code__in=school_codes
    )

    if examtype:
        perf_qs = perf_qs.filter(exam_type_id=examtype)

    if subject:
        perf_qs = perf_qs.filter(subject_code=subject)
    if start_year:
        perf_qs = perf_qs.filter(year__gte=int(start_year))
    if end_year:
        perf_qs = perf_qs.filter(year__lte=int(end_year))

    # =====================================================
    # 3. KPI COUNTS
    # =====================================================
    grade_kpi_qs = SchoolSubjectGradeSummary.objects.filter(
        school__school_code__in=school_codes,
        grade__in=["A", "B", "C", "D", "F"]   # ✅ exclude X
    )

    if examtype:
        grade_kpi_qs = grade_kpi_qs.filter(exam_type_id=examtype)
    if subject:
        grade_kpi_qs = grade_kpi_qs.filter(subject_code=subject)
    if start_year:
        grade_kpi_qs = grade_kpi_qs.filter(year__gte=int(start_year))
    if end_year:
        grade_kpi_qs = grade_kpi_qs.filter(year__lte=int(end_year))
    if gender in ["M", "F"]:
        grade_kpi_qs = grade_kpi_qs.filter(sex=gender)

    total_students = grade_kpi_qs.aggregate(
        t=Sum("total")
    )["t"] or 0

    pass_count = grade_kpi_qs.filter(
        grade__in=["A", "B", "C", "D"]
    ).aggregate(
        t=Sum("total")
    )["t"] or 0

    fail_count = grade_kpi_qs.filter(
        grade="F"
    ).aggregate(
        t=Sum("total")
    )["t"] or 0

    # =====================================================
    # 4. GRADE DISTRIBUTION (FROM SUMMARY TABLE)
    # =====================================================
    grade_qs = SchoolSubjectGradeSummary.objects.filter(
        school__school_code__in=school_codes
    )

    if examtype:
        grade_qs = grade_qs.filter(exam_type_id=examtype)

    if subject:
        grade_qs = grade_qs.filter(subject_code=subject)
    if start_year:
        grade_qs = grade_qs.filter(year__gte=int(start_year))
    if end_year:
        grade_qs = grade_qs.filter(year__lte=int(end_year))
    if gender in ["M","F"]:
        grade_qs = grade_qs.filter(sex=gender)

    grades = ["A","B","C","D","F"]

    grade_values = [
        grade_qs.filter(grade=g).aggregate(t=Sum("total"))["t"] or 0
        for g in grades
    ]

    # =====================================================
    # 5. YEARLY AVERAGE GPA (LINE)
    # =====================================================
    SUB_WEIGHT = {
        "A": 1,
        "B": 2,
        "C": 3,
        "D": 4,
        "F": 5,
    }

    yearly_qs = SchoolSubjectGradeSummary.objects.filter(
        school__school_code__in=school_codes,
        grade__in=["A", "B", "C", "D", "F"]   # exclude X
    )

    if examtype:
        yearly_qs = yearly_qs.filter(exam_type_id=examtype)
    if subject:
        yearly_qs = yearly_qs.filter(subject_code=subject)
    if start_year:
        yearly_qs = yearly_qs.filter(year__gte=int(start_year))
    if end_year:
        yearly_qs = yearly_qs.filter(year__lte=int(end_year))
    if gender in ["M", "F"]:
        yearly_qs = yearly_qs.filter(sex=gender)


    yearly_stats = {}

    for r in yearly_qs:
        y = r.year
        w = SUB_WEIGHT[r.grade]

        if y not in yearly_stats:
            yearly_stats[y] = {
                "weighted": 0,
                "total": 0
            }

        yearly_stats[y]["weighted"] += w * r.total
        yearly_stats[y]["total"] += r.total

    years = []
    avg_series = []

    for y in sorted(yearly_stats.keys()):
        t = yearly_stats[y]["total"]
        w = yearly_stats[y]["weighted"]

        gpa = round(w / t, 4) if t else 0

        years.append(y)
        avg_series.append(gpa)

    overall_weighted = sum(v["weighted"] for v in yearly_stats.values())
    overall_total = sum(v["total"] for v in yearly_stats.values())

    avg_overall = round(overall_weighted / overall_total, 4) if overall_total else 0

    # =====================================================
    # 7. STACKED BAR: GRADES BY SUBJECT
    # =====================================================
    # ---------------- STACKED SUBJECT GRADES ----------------
    stack_qs = SchoolSubjectGradeSummary.objects.filter(
        school__school_code__in=school_codes
    )

    if examtype:
        stack_qs = stack_qs.filter(exam_type_id=examtype)
    if subject:
        stack_qs = stack_qs.filter(subject_code=subject)
    if start_year:
        stack_qs = stack_qs.filter(year__gte=int(start_year))
    if end_year:
        stack_qs = stack_qs.filter(year__lte=int(end_year))
    if gender in ["M","F"]:
        stack_qs = stack_qs.filter(sex=gender)

    stack_subjects = list(
        stack_qs.values_list("subject_code", flat=True).distinct()
    )

    subject_names = {
        s.subject_code: s.subject_name_eng
        for s in ExamSubject.objects.all()
    }

    grades_stack = ["A","B","C","D","F"]

    stack_values = {g: [] for g in grades_stack}

    for sub in stack_subjects:
        for g in grades_stack:
            v = stack_qs.filter(subject_code=sub, grade=g)\
                        .aggregate(t=Sum("total"))["t"] or 0
            stack_values[g].append(v)

    stack_labels = [subject_names.get(s, s) for s in stack_subjects]

    # =====================================================
    # 6. FINAL JSON
    # =====================================================
    return JsonResponse({
        "cards": [
            {"value": total_students},
            {"value": pass_count},
            {"value": fail_count},
            {"value": round(avg_overall, 4)},
        ],
        "donut": [
            {"name": "Pass", "value": pass_count},
            {"name": "Fail", "value": fail_count},
        ],
        "grades": {
            "labels": grades,
            "values": grade_values,
        },
        "line": {
            "years": years,
            "avg_marks": avg_series,
        },
        "stacked": {   # 🔥 NEW PART
            "subjects": stack_labels,
            "grades": grades_stack,
            "values": stack_values
        }
    })
























from django.contrib import messages
from django.shortcuts import redirect, render
from django.contrib.auth.decorators import login_required, permission_required
import openpyxl
import csv
import io

@login_required
@permission_required("exams.add_school", raise_exception=True)
def geo_import_upload(request):

    if request.method == "POST":
        file = request.FILES.get("geo_file")

        if not file:
            messages.error(request, "No file selected.")
            return redirect("exams:geo_import")

        try:
            import pandas as pd
            from exams.services.geo_importer import import_geo_dataframe

            if file.name.endswith(".xlsx"):
                df = pd.read_excel(file)
            elif file.name.endswith(".csv"):
                df = pd.read_csv(file)
            else:
                messages.error(request, "Only Excel (.xlsx) or CSV allowed.")
                return redirect("exams:geo_import")

            result = import_geo_dataframe(df)

            messages.success(
                request,
                f"Import completed: "
                f"{result['regions']} regions, "
                f"{result['districts']} districts, "
                f"{result['wards']} wards, "
                f"{result['schools']} schools."
            )

        except Exception as e:
            messages.error(request, f"Import failed: {e}")

        return redirect("exams:geo_import")

    schools = School.objects.select_related(
        "region", "district", "ward"
    ).order_by("name")

    return render(request, "exams/geo_import.html", {
        "schools": schools
    })



@login_required
@permission_required("exams.delete_school", raise_exception=True)
def school_delete(request, pk):
    school = get_object_or_404(School, pk=pk)

    if not school.can_delete():
        messages.error(request, "Cannot delete school with existing data.")
        return redirect("exams:geo_import")

    school.delete()
    messages.success(request, "School deleted successfully.")
    return redirect("exams:geo_import")


from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required

from exams.models import School, District, Ward
from exams.forms import SchoolForm


@login_required
@permission_required("exams.change_school", raise_exception=True)
def school_edit(request, pk):
    school = get_object_or_404(School, pk=pk)

    if request.method == "POST":
        form = SchoolForm(request.POST, instance=school)

        if form.is_valid():
            form.save()
            messages.success(request, "School details updated successfully.")
            return redirect("exams:geo_import")

        messages.error(request, "Please correct the errors below.")

    else:
        form = SchoolForm(instance=school)

    return render(request, "exams/school_edit.html", {
        "form": form,
        "school": school,
    })



import openpyxl
from openpyxl.styles import Font
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required, permission_required

from exams.models import School


@login_required
@permission_required("exams.view_school", raise_exception=True)
def export_schools_excel(request):
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Schools"

    # Header
    headers = [
        "School Code",
        "Registration No",
        "School Name",
        "Ward",
        "District",
        "Region",
        "Ownership",
        "Latitude",
        "Longitude",
    ]
    ws.append(headers)

    # Bold header
    for cell in ws[1]:
        cell.font = Font(bold=True)

    schools = School.objects.select_related(
        "region", "district", "ward"
    ).order_by("region__name", "district__name", "ward__name", "name")

    for s in schools:
        ws.append([
            s.school_code,
            s.school_regno or "",
            s.name,
            s.ward.name,
            s.district.name,
            s.region.name,
            s.ownership,
            float(s.latitude) if s.latitude is not None else "",
            float(s.longitude) if s.longitude is not None else "",
        ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = 'attachment; filename="schools_list.xlsx"'
    wb.save(response)

    return response


@login_required
@permission_required("exams.view_examtype", raise_exception=True)
def examtype_list(request):
    examtypes = ExamType.objects.all()
    return render(request, "exams/examtype_list.html", {
        "examtypes": examtypes
    })


@login_required
@permission_required("exams.add_examtype", raise_exception=True)
def examtype_create(request):
    if request.method == "POST":
        form = ExamTypeForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "Exam type created successfully.")
            return redirect("exams:examtype_list")
    else:
        form = ExamTypeForm()

    return render(request, "exams/examtype_form.html", {
        "form": form,
        "title": "Add Exam Type"
    })


@login_required
@permission_required("exams.change_examtype", raise_exception=True)
def examtype_edit(request, pk):
    examtype = get_object_or_404(ExamType, pk=pk)

    if request.method == "POST":
        form = ExamTypeForm(request.POST, instance=examtype)
        if form.is_valid():
            form.save()
            messages.success(request, "Exam type updated successfully.")
            return redirect("exams:examtype_list")
    else:
        form = ExamTypeForm(instance=examtype)

    return render(request, "exams/examtype_form.html", {
        "form": form,
        "title": "Edit Exam Type"
    })


@login_required
@permission_required("exams.delete_examtype", raise_exception=True)
def examtype_delete(request, pk):
    examtype = get_object_or_404(ExamType, pk=pk)

    # Optional safety check
    if examtype.schoolyearsummary_set.exists():
        messages.error(
            request,
            "Cannot delete exam type already used in results."
        )
        return redirect("exams:examtype_list")

    examtype.delete()
    messages.success(request, "Exam type deleted successfully.")
    return redirect("exams:examtype_list")



from exams.forms import ExamSubjectForm

@login_required
@permission_required("exams.view_examsubject", raise_exception=True)
def subject_list(request):
    subjects = ExamSubject.objects.all()
    return render(request, "exams/subject_list.html", {
        "subjects": subjects
    })


@login_required
@permission_required("exams.add_examsubject", raise_exception=True)
def subject_create(request):
    if request.method == "POST":
        form = ExamSubjectForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "Subject added successfully.")
            return redirect("exams:subject_list")
    else:
        form = ExamSubjectForm()

    return render(request, "exams/subject_form.html", {
        "form": form,
        "title": "Add Subject"
    })


@login_required
@permission_required("exams.change_examsubject", raise_exception=True)
def subject_edit(request, pk):
    subject = get_object_or_404(ExamSubject, pk=pk)

    if request.method == "POST":
        form = ExamSubjectForm(request.POST, instance=subject)
        if form.is_valid():
            form.save()
            messages.success(request, "Subject updated successfully.")
            return redirect("exams:subject_list")
    else:
        form = ExamSubjectForm(instance=subject)

    return render(request, "exams/subject_form.html", {
        "form": form,
        "title": "Edit Subject"
    })


@login_required
@permission_required("exams.delete_examsubject", raise_exception=True)
def subject_delete(request, pk):
    subject = get_object_or_404(ExamSubject, pk=pk)

    # SAFE CHECK: subject used anywhere?
    used_in_marks = StudentSubjectMark.objects.filter(
        subject_code=subject.subject_code
    ).exists()

    used_in_summary = SchoolSubjectPerformance.objects.filter(
        subject_code=subject.subject_code
    ).exists()

    if used_in_marks or used_in_summary:
        messages.error(
            request,
            "Cannot delete subject because it is already used in exam data."
        )
        return redirect("exams:subject_list")

    subject.delete()
    messages.success(request, "Subject deleted successfully.")
    return redirect("exams:subject_list")




import openpyxl
from django.contrib import messages
from django.db import transaction
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required, permission_required

from exams.models import ExamSubject


@login_required
@permission_required("exams.add_examsubject", raise_exception=True)
def subject_bulk_upload(request):
    if request.method != "POST":
        return redirect("exams:subject_list")

    excel_file = request.FILES.get("excel_file")
    if not excel_file:
        messages.error(request, "No file uploaded.")
        return redirect("exams:subject_list")

    if not excel_file.name.endswith(".xlsx"):
        messages.error(request, "Only Excel (.xlsx) files are allowed.")
        return redirect("exams:subject_list")

    wb = openpyxl.load_workbook(excel_file)
    ws = wb.active

    headers = [str(c.value).strip() for c in ws[1]]
    required_cols = [
        "subject_code",
        "subject_name_eng",
        "subject_name_sw",
        "subject_shortname",
        "subject_level",
        "is_practical",
        "is_principal",
        "number_of_papers",
        "color_code",
        "academic_level_id",
    ]

    for col in required_cols:
        if col not in headers:
            messages.error(request, f"Missing column: {col}")
            return redirect("exams:subject_list")

    created, updated, errors = 0, 0, []

    with transaction.atomic():
        for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True), start=2):
            data = dict(zip(headers, row))

            code = str(data.get("subject_code", "")).strip()
            name_eng = str(data.get("subject_name_eng", "")).strip()
            name_sw = str(data.get("subject_name_sw", "")).strip()
            shortname = str(data.get("subject_shortname", "")).strip()
            level = str(data.get("subject_level", "")).strip()
            is_practical = str(data.get("is_practical", "")).strip()
            is_principal = str(data.get("is_principal", "")).strip()
            number_of_papers = str(data.get("number_of_papers", "")).strip()
            color_code = str(data.get("color_code", "")).strip()
            academic_level_id = str(data.get("academic_level_id", "")).strip()
            is_active = data.get("is_active", True)

            if not code or not name_eng or not name_sw:
                errors.append(f"Row {i}: Missing required values")
                continue

            active = True
            if is_active in [0, "0", "FALSE", "false", False]:
                active = False

            obj, created_flag = ExamSubject.objects.update_or_create(
                subject_code=code,
                defaults={
                    "subject_name_eng": name_eng,
                    "subject_name_sw": name_sw,
                    "subject_shortname": shortname,
                    "subject_level": level,
                    "is_practical": is_practical,
                    "is_principal": is_principal,
                    "number_of_papers": number_of_papers,
                    "color_code": color_code,
                    "academic_level_id": academic_level_id,
                    "is_active": active,
                }
            )

            if created_flag:
                created += 1
            else:
                updated += 1

    messages.success(
        request,
        f"Bulk upload completed: {created} created, {updated} updated."
    )

    if errors:
        messages.warning(
            request,
            "Some rows were skipped: " + "; ".join(errors[:5])
        )

    return redirect("exams:subject_list")


@login_required
def subject_template_download(request):
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Subjects"

    ws.append([
        "subject_code",
        "subject_name_eng",
        "subject_name_sw",
        "subject_shortname",
        "subject_level",
        "is_practical",
        "is_principal",
        "number_of_papers",
        "color_code",
        "academic_level_id",
        "is_active"
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = 'attachment; filename="subjects_template.xlsx"'
    wb.save(response)
    return response








@login_required
def download_template(request, exam_id):
    exam = get_object_or_404(ExamType, id=exam_id)

    file_path = os.path.join(
        settings.MEDIA_ROOT,
        "templates",
        f"{exam.code}_UPLOAD_TEMPLATE.xlsx"
    )

    if not os.path.exists(file_path):
        raise Http404("Template not found")

    return FileResponse(
        open(file_path, "rb"),
        as_attachment=True,
        filename=os.path.basename(file_path)
    )


@login_required
def upload_results(request, exam_id):
    exam = get_object_or_404(ExamType, id=exam_id)

    if request.method == "POST":
        # handle Excel upload here
        pass

    return render(request, "upload_results.html", {"exam": exam})





@login_required
def exam_schools(request, exam_id):
    exam = get_object_or_404(ExamType, id=exam_id)
    user = request.user
    profile = getattr(user, "profile", None)

    # 🔹 Superuser sees all schools
    if user.is_superuser:
        schools = School.objects.all()

    # 🔹 Role-based filtering for non-superusers
    elif profile:
        role = getattr(profile, "role", None)

        if role == "region":
            # All schools in the user's region
            schools = School.objects.filter(region=profile.region)
        
        elif role == "district":
            # All schools in the user's district
            schools = School.objects.filter(district=profile.district)
        
        elif role == "ward":
            # All schools in the user's ward
            schools = School.objects.filter(ward=profile.ward)
        
        elif role == "school":
            # Only the user's school
            if profile.school:
                schools = School.objects.filter(pk=profile.school.pk)
            else:
                schools = School.objects.none()
        
        else:
            # Unknown role → no schools
            schools = School.objects.none()

    else:
        # No profile & not superuser → no schools
        schools = School.objects.none()

    # Optimize foreign key access
    schools = schools.select_related("region", "district", "ward").order_by("school_code")

    context = {
        "exam": exam,
        "schools": schools,
        "user_profile": profile,  # optional for template use
    }

    return render(request, "exams/exam_schools.html", context)









@login_required
def school_actions(request, exam_id, school_code):
    """
    Dashboard actions for a selected school & exam
    """

    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    candidates_count = ExamCandidate.objects.filter(
        exam_type=exam,
        school=school
    ).count()

    context = {
        "exam": exam,
        "school": school,
        "candidates_count": candidates_count,
    }

    return render(request, "school_actions.html", context)





import csv

# ---------------- Print school results
@login_required
def print_school_results(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)
    
    # Fetch results for this school and exam
    results = school.results.filter(exam_type=exam)  # adjust 'results' field/model as per your setup
    
    context = {
        "exam": exam,
        "school": school,
        "results": results,
    }
    return render(request, "exams/print_school_results.html", context)


# ---------------- Print students
@login_required
def print_students(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)
    
    students = school.students.all()  # adjust if your Student model is different
    
    context = {
        "exam": exam,
        "school": school,
        "students": students,
    }
    return render(request, "exams/print_students.html", context)


# ---------------- Download reports (CSV)
@login_required
def download_reports(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)
    
    results = school.results.filter(exam_type=exam)  # adjust as needed

    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = f'attachment; filename="{school.school_code}_{exam.name}_reports.csv"'

    writer = csv.writer(response)
    writer.writerow(['Student No', 'Name', 'Subject', 'Grade', 'Score'])

    for r in results:
        writer.writerow([r.student.student_number, r.student.name, r.subject.name, r.grade, r.numeric_score])

    return response


# ---------------- Download Bulk SMS (CSV)
@login_required
def download_bulk_sms(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)
    
    students = school.students.all()  # adjust field names
    response = HttpResponse(content_type='text/csv')
    response['Content-Disposition'] = f'attachment; filename="{school.school_code}_{exam.name}_bulk_sms.csv"'

    writer = csv.writer(response)
    writer.writerow(['Phone Number', 'Message'])

    for student in students:
        # Example message
        message = f"{student.name}, your {exam.name} results are ready."
        writer.writerow([student.phone_number, message])

    return response


import csv

@login_required
def print_school_results(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    # 🔹 1️⃣ Get all candidates for this school and exam
    candidates = ExamCandidate.objects.filter(exam_type=exam, school=school).order_by('cand_no')

    # 🔹 2️⃣ Get all StudentSubjectMarks for this school and exam
    all_marks = StudentSubjectMark.objects.filter(
        exam_type=exam,
        school=school,
        numeric_score__isnull=False  # only subjects with at least one mark
    ).values('subject_code').distinct()

    # Get subject codes that have at least one mark
    subject_codes_with_marks = [m['subject_code'] for m in all_marks]

    # 🔹 3️⃣ Fetch subject details only for those codes
    subjects = ExamSubject.objects.filter(
        subject_code__in=subject_codes_with_marks,
        is_active=True
    ).order_by('subject_code')

    # 🔹 4️⃣ Build candidate results
    results = []
    for cand in candidates:
        row = {
            "school_code": school.school_code,
            "cand_no": cand.cand_no,
            "name": cand.name,
            "sex": cand.sex,
            "subjects": [],
            "points": cand.points,
            "division": cand.division,
            "average": cand.avg_numeric,
            "avg_grade": cand.avg_grade,
            "position": getattr(cand, "position", ""),
        }

        # Get marks for this candidate only for subjects that exist
        marks = StudentSubjectMark.objects.filter(
            exam_type=exam,
            school=school,
            cand_no=cand.cand_no,
            subject_code__in=subject_codes_with_marks
        ).values('subject_code', 'numeric_score', 'grade')

        marks_dict = {m['subject_code']: m for m in marks}

        # Fill subjects in order
        for subj in subjects:
            mark = marks_dict.get(subj.subject_code)
            if mark:
                row["subjects"].append({
                    "numeric_score": mark['numeric_score'],
                    "grade": mark['grade']
                })
            else:
                row["subjects"].append({
                    "numeric_score": "",
                    "grade": ""
                })

        results.append(row)

    context = {
        "exam": exam,
        "school": school,
        "subjects": subjects,
        "results": results,
    }

    return render(request, "exams/print_school_results.html", context)










@login_required
def download_school_results_excel(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    # Subjects with at least one mark
    subject_codes = (
        StudentSubjectMark.objects
        .filter(exam_type=exam, school=school, numeric_score__isnull=False)
        .values_list("subject_code", flat=True)
        .distinct()
    )

    subjects = ExamSubject.objects.filter(
        subject_code__in=subject_codes,
        is_active=True
    ).order_by("subject_code")

    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = (
        f'attachment; filename="{school.school_code}_{exam.code}_results.csv"'
    )

    writer = csv.writer(response)

    # Header
    header = [
        "SCHOOL", "CAND NO", "NAME", "SEX"
    ]

    for s in subjects:
        short = s.subject_shortname or s.subject_code
        header.append(VerticalText(f"{short} SCORE"))
        header.append(VerticalText(f"{short} GRADE"))

    header += ["POINT", "DIV", "AVG", "GRADE", "POS"]

    writer.writerow(header)

    candidates = ExamCandidate.objects.filter(
        exam_type=exam, school=school
    ).order_by("cand_no")

    for cand in candidates:
        marks = StudentSubjectMark.objects.filter(
            exam_type=exam,
            school=school,
            cand_no=cand.cand_no,
            subject_code__in=subject_codes
        ).values("subject_code", "numeric_score", "grade")

        mark_map = {m["subject_code"]: m for m in marks}

        row = [
            school.school_code,
            cand.cand_no,
            cand.name,
            cand.sex
        ]

        for s in subjects:
            m = mark_map.get(s.subject_code)
            row.append(round_no_decimal(m["numeric_score"]) if m else "")
            row.append(m["grade"] if m else "")

        row += [
            cand.points,
            cand.division,
            round_no_decimal(cand.avg_numeric),
            cand.avg_grade,
            getattr(cand, "position", "")
        ]


        writer.writerow(row)

    return response







@login_required
def download_school_results_pdf(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    # Subjects that have at least one mark (PERFORMANCE OPTIMIZED)
    subject_codes = (
        StudentSubjectMark.objects
        .filter(exam_type=exam, school=school, numeric_score__isnull=False)
        .values_list("subject_code", flat=True)
        .distinct()
    )

    subjects = list(
        ExamSubject.objects
        .filter(subject_code__in=subject_codes, is_active=True)
        .order_by("subject_code")
    )

    candidates = ExamCandidate.objects.filter(
        exam_type=exam, school=school
    ).order_by("cand_no")

    # PDF response
    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="{school.school_code}_{exam.code}_results.pdf"'
    )


    # =====================================================
    # 1️⃣ DEFINE COLUMN WIDTHS FIRST  (CRITICAL FIX)
    # =====================================================
    col_widths = [
        40,   # SCHOOL
        60,   # CAND NO
        140,  # NAME
        30,   # SEX
    ]

    for _ in subjects:
        col_widths.extend([18, 18])  # SCORE / GRADE

    col_widths.extend([35, 35, 35, 40, 35])  # tail columns

    LEFT_MARGIN = 15
    RIGHT_MARGIN = 15

    table_width = sum(col_widths)

    a4_width = landscape(A4)[0] - LEFT_MARGIN - RIGHT_MARGIN
    a3_width = landscape(A3)[0] - LEFT_MARGIN - RIGHT_MARGIN

    if table_width > a4_width:
        page_size = landscape(A3)
    else:
        page_size = landscape(A4)

    if table_width > a3_width:
        shrink_ratio = a3_width / table_width
        col_widths = [w * shrink_ratio for w in col_widths]

    doc = SimpleDocTemplate(
        response,
        pagesize=page_size,
        leftMargin=LEFT_MARGIN,
        rightMargin=RIGHT_MARGIN,
        topMargin=20,
        bottomMargin=25,
    )



    styles = getSampleStyleSheet()
    story = []

    # 🔹 Header
    title = f"{school.name} ({school.school_code}) – {exam.name}"
    story.append(Paragraph(f"<b>{title}</b>", styles["Title"]))
    story.append(Spacer(1, 10))

    # 🔹 Build table header
    table_data = []

    header = [
        "SCHOOL", "CAND NO", "NAME", "SEX"
    ]

    for s in subjects:
        short = s.subject_shortname or s.subject_code
        header.append(VerticalText(f"{short} SC"))
        header.append(VerticalText(f"{short} GRD"))

    header += ["POINT", "DIV", "AVG", "GRADE", "POS"]


    table_data.append(header)

    # 🔹 Fill table rows
    for cand in candidates:
        marks = StudentSubjectMark.objects.filter(
            exam_type=exam,
            school=school,
            cand_no=cand.cand_no,
            subject_code__in=subject_codes
        ).values("subject_code", "numeric_score", "grade")

        mark_map = {m["subject_code"]: m for m in marks}

        row = [
            school.school_code,
            cand.cand_no,
            cand.name,
            cand.sex,
        ]

        for s in subjects:
            m = mark_map.get(s.subject_code)
            row.append(round_no_decimal(m["numeric_score"]) if m else "")
            row.append(m["grade"] if m else "")

        row += [
            cand.points,
            cand.division,
            round_no_decimal(cand.avg_numeric),
            cand.avg_grade,
            getattr(cand, "position", "")
        ]



        table_data.append(row)

    # 🔹 Create table
    # table = Table(table_data, repeatRows=1, rowHeights=[40] + [None]*(len(table_data)-1))

    HEADER_HEIGHT = 60
    row_heights = [HEADER_HEIGHT] + [None] * (len(table_data) - 1)



    table = Table(
        table_data,
        repeatRows=1,
        colWidths=col_widths,
        rowHeights=row_heights
    )

    table.setStyle(TableStyle([
        ("GRID", (0, 0), (-1, -1), 0.25, colors.black),
        ("BACKGROUND", (0, 0), (-1, 0), colors.lightgrey),
        ("ALIGN", (0, 0), (-1, 0), "CENTER"),
        ("ALIGN", (4, 1), (-1, -1), "CENTER"),
        ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
        ("FONTSIZE", (0, 0), (-1, -1), 7),
        ("TOPPADDING", (0, 0), (-1, 0), 4),
        ("BOTTOMPADDING", (0, 0), (-1, 0), 4),
    ]))


    story.append(table)

    # 🔹 Footer
    story.append(Spacer(1, 12))
    footer = f"{exam.code} {exam.year} – {school.name}"
    story.append(Paragraph(footer, styles["Normal"]))

    # Build PDF (reuse your existing page decorator)
    doc.build(story, onFirstPage=_on_page, onLaterPages=_on_page)

    return response



@login_required
def school_results_html(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    subject_codes = (
        StudentSubjectMark.objects
        .filter(exam_type=exam, school=school, numeric_score__isnull=False)
        .values_list("subject_code", flat=True)
        .distinct()
    )

    subjects = list(
        ExamSubject.objects
        .filter(subject_code__in=subject_codes, is_active=True)
        .order_by("subject_code")
    )

    candidates = ExamCandidate.objects.filter(
        exam_type=exam, school=school
    ).order_by("cand_no")

    # Preload all marks ONCE (critical for speed)
    marks_qs = StudentSubjectMark.objects.filter(
        exam_type=exam,
        school=school,
        subject_code__in=subject_codes
    ).values("cand_no", "subject_code", "numeric_score", "grade")

    marks_map = {}
    for m in marks_qs:
        marks_map.setdefault(m["cand_no"], {})[m["subject_code"]] = m

    rows = []
    for cand in candidates:
        row = {
            "school": school.school_code,
            "cand_no": cand.cand_no,
            "name": cand.name,
            "sex": cand.sex,
            "subjects": [],
            "points": cand.points,
            "division": cand.division,
            "avg": round_no_decimal(cand.avg_numeric),
            "grade": cand.avg_grade,
            "pos": getattr(cand, "position", "")
        }

        cand_marks = marks_map.get(cand.cand_no, {})
        for s in subjects:
            m = cand_marks.get(s.subject_code)
            row["subjects"].append({
                "score": round_no_decimal(m["numeric_score"]) if m else "",
                "grade": m["grade"] if m else ""
            })

        rows.append(row)

    return render(request, "exams/school_results_fast.html", {
        "exam": exam,
        "school": school,
        "subjects": subjects,
        "rows": rows,
    })





from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from openpyxl import Workbook

@login_required
def export_sms_xlsx(request, exam_id, school_code):
    exam = get_object_or_404(ExamType, id=exam_id)
    school = get_object_or_404(School, school_code=school_code)

    # ✅ AUTO-DETECT LATEST YEAR
    year = (
        ExamCandidate.objects
        .filter(exam_type=exam, school=school)
        .values_list("year", flat=True)
        .order_by("-year")
        .first()
    )

    if not year:
        messages.error(request, "No results found for this school and exam.")
        return redirect("exams:dashboard")

    candidates = ExamCandidate.objects.filter(
        exam_type=exam,
        school=school,
        year=year
    ).order_by("cand_no")

    # -------------------------
    # BUILD SMS CONTENT
    # -------------------------
    rows = []
    for c in candidates:


        points = int(c.avg_numeric) if c.avg_numeric is not None else "N/A"
        division = c.avg_grade or "N/A"
        full_results = c.full_results or "Results pending"

        sms = (
            f"Habari Mzazi/Mlezi wa  {c.name}, Matokeo ya Mitihani ya {exam.name} "
            f"ni Division {division}, Points {points}. "
            f"Matokeo kwa Ujumla ni: {full_results}"
        )


        rows.append({
            "CAND_NO": c.cand_no,
            "NAME": c.name,
            "PHONE": "",  # add if available
            "SMS": sms,
        })

    # -------------------------
    # EXPORT TO EXCEL
    # -------------------------
    from django.http import HttpResponse
    from openpyxl import Workbook

    wb = Workbook()
    ws = wb.active
    ws.title = "Data"

    # Write header (from dict keys)
    if rows:
        headers = list(rows[0].keys())
        ws.append(headers)

        # Write data rows
        for row in rows:
            ws.append([row.get(h) for h in headers])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="{exam.code}_{school.school_code}_SMS.xlsx"'
    )

    wb.save(response)
    return response




















from django.shortcuts import render
from django.contrib import messages

from .models import (
    SchoolClass,
    Stream,
    Teacher,
    TeacherSubject,
    TimetableSubject,
)
from .forms import (
    SchoolClassForm,
    StreamForm,
    TeacherForm,
    TimetableSubjectForm,
    TeacherSubjectForm,
)
from .services.timetable_engine import generate_timetable


@login_required
def timetable_view(request):

    user = request.user
    profile = getattr(user, "profile", None)

    if not profile or not profile.school:
        messages.error(request, "Your account is not linked to any school.")
        return redirect("exams:dashboard")   # or any safe page

    school = profile.school

    classes = SchoolClass.objects.filter(school=school)
    streams = Stream.objects.filter(school=school)
    subjects = TimetableSubject.objects.filter(school=school)
    teachers = Teacher.objects.filter(school=school)
    teacher_subjects = TeacherSubject.objects.filter(school=school)


    DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
    PERIODS = [
        "P1", "P2", "P3", "P4", "BREAK",
        "P6", "P7", "P8", "P9", "LUNCH",
        "P11", "P12"
    ]

    timetable = None
    display_table = []

    # -------------------------------------------------
    # FORMS (DEFAULT)
    # -------------------------------------------------
    class_form = SchoolClassForm()
    stream_form = StreamForm()
    teacher_form = TeacherForm()
    subject_form = TimetableSubjectForm()
    assign_form = TeacherSubjectForm()

    # -------------------------------------------------
    # HANDLE POST ACTIONS
    # -------------------------------------------------
    if request.method == "POST":

        # ---------- ADD CLASS ----------
        if "add_class" in request.POST:
            class_form = SchoolClassForm(request.POST, school=school)
            if class_form.is_valid():
                class_form.save()
                request.session.pop("timetable", None)
                messages.success(
                    request,
                    "Class added. Timetable needs regeneration."
                )
            else:
                messages.error(request, "Failed to add class.")

        # ---------- ADD STREAM ----------
        elif "add_stream" in request.POST:
            stream_form = StreamForm(request.POST, school=school)
            if stream_form.is_valid():
                stream_form.save()
                request.session.pop("timetable", None)
                messages.success(
                    request,
                    "Stream added. Timetable needs regeneration."
                )
            else:
                messages.error(request, "Failed to add stream.")

        # ---------- ADD TEACHER ----------
        elif "add_teacher" in request.POST:
            teacher_form = TeacherForm(request.POST, school=school)
            if teacher_form.is_valid():
                teacher_form.save()
                request.session.pop("timetable", None)
                messages.success(
                    request,
                    "Teacher added. Timetable needs regeneration."
                )
            else:
                messages.error(request, "Failed to add teacher.")

        # ---------- ADD SUBJECT ----------
        elif "add_subject" in request.POST:
            subject_form = TimetableSubjectForm(request.POST, school=school)
            if subject_form.is_valid():
                subject_form.save()
                request.session.pop("timetable", None)
                messages.success(
                    request,
                    "Subject added. Timetable needs regeneration."
                )
            else:
                messages.error(request, "Failed to add subject.")

        # ---------- ASSIGN TEACHER ----------
        elif "assign_teacher" in request.POST:
            assign_form = TeacherSubjectForm(request.POST, school=school)
            if assign_form.is_valid():
                assign_form.save()
                request.session.pop("timetable", None)
                messages.success(
                    request,
                    "Teacher assigned. Timetable needs regeneration."
                )
            else:
                messages.error(request, "Assignment failed.")

        # ---------- GENERATE TIMETABLE ----------
        elif "generate" in request.POST:

            # VALIDATION (MUST COME FIRST)
            if not streams.exists():
                messages.error(request, "No streams found. Please add streams first.")

            elif not subjects.exists():
                messages.error(request, "No timetable subjects found.")

            elif not teachers.exists():
                messages.error(request, "No teachers found.")

            elif not teacher_subjects.exists():
                messages.error(
                    request,
                    "No teacher-subject-stream assignments found."
                )

            else:
                timetable = generate_timetable(
                    streams, subjects, teacher_subjects
                )

                

                # ✅ STORE REAL TIMETABLE IN SESSION
                request.session["timetable"] = timetable
                request.session.modified = True
                # ✅ STORE DISPLAY TABLE FOR PDF & EXCEL
                request.session["display_table"] = display_table
                request.session.modified = True

                messages.success(
                    request,
                    "Timetable generated successfully."
                )

    # -------------------------------------------------
    # LOAD TIMETABLE FROM SESSION (ALWAYS)
    # -------------------------------------------------
    timetable = request.session.get("timetable")

    # -------------------------------------------------
    # PREPARE TEMPLATE-READY DATA
    # -------------------------------------------------
    if timetable:
        for day, stream_map in timetable.items():
            for stream_id, periods_map in stream_map.items():

                stream_obj = next(
                    (s for s in streams if s.id == stream_id),
                    None
                )
                if not stream_obj:
                    continue

                row = {
                    "day": day,
                    "stream": str(stream_obj),
                    "cells": []
                }

                for period in PERIODS:
                    cell = periods_map.get(period)

                    if isinstance(cell, dict):
                        row["cells"].append({
                            "name": cell.get("name", ""),
                            "color": cell.get("color", "#FFFFFF"),
                        })
                    elif cell in ["BREAK", "LUNCH"]:
                        row["cells"].append({
                            "name": cell,
                            "color": "#26ffe9",
                        })
                    else:
                        row["cells"].append({
                            "name": cell or "",
                            "color": "#FFFFFF",
                        })

                display_table.append(row)

    # -------------------------------------------------
    # CONTEXT
    # -------------------------------------------------
    context = {
        "classes": classes,
        "streams": streams,
        "subjects": subjects,
        "teachers": teachers,
        "display_table": display_table,
        "days": DAYS,
        "periods": PERIODS,
        "class_form": class_form,
        "stream_form": stream_form,
        "teacher_form": teacher_form,
        "subject_form": subject_form,
        "assign_form": assign_form,
        "school": school,   # 🔴 THIS MUST EXIST
    }
    return render(request, "exams/timetable.html", context)








from django.http import HttpResponse
from reportlab.lib.pagesizes import A3, landscape
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors


def timetable_pdf(request):

    school = request.user.profile.school
    display_table = request.session.get("display_table", [])

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'attachment; filename="{school.school_code}_TIMETABLE.pdf"'
    )

    doc = SimpleDocTemplate(
        response,
        pagesize=landscape(A3),
        rightMargin=20,
        leftMargin=20,
        topMargin=20,
        bottomMargin=20,
    )

    styles = getSampleStyleSheet()
    elements = []

    # -------------------------------------------------
    # HEADER
    # -------------------------------------------------
    title = Paragraph(
        f"<b>{school.school_code} - {school.name}</b>",
        styles["Title"],
    )
    subtitle = Paragraph(
        f"{school.region} | {school.district} | {school.ward}",
        styles["Normal"],
    )

    elements.append(title)
    elements.append(subtitle)
    elements.append(Spacer(1, 12))

    # -------------------------------------------------
    # TABLE DATA
    # -------------------------------------------------
    headers = [
        "Day","Stream",
        "P1","P2","P3","P4","BREAK",
        "P6","P7","P8","P9","LUNCH",
        "P11","P12"
    ]

    data = [headers]

    for row in display_table:
        data.append(
            [row["day"], row["stream"]] +
            [c["name"] or "-" for c in row["cells"]]
        )

    table = Table(data, repeatRows=1)

    # -------------------------------------------------
    # BASE TABLE STYLE
    # -------------------------------------------------
    style = [
        ("GRID", (0,0), (-1,-1), 0.5, colors.black),
        ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
        ("ALIGN", (2,1), (-1,-1), "CENTER"),
        ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
        ("FONT", (0,0), (-1,0), "Helvetica-Bold"),
        ("FONT", (0,1), (-1,-1), "Helvetica"),
        ("FONTSIZE", (0,0), (-1,-1), 8),
    ]

    # -------------------------------------------------
    # APPLY SUBJECT COLORS FROM DB
    # -------------------------------------------------
    def hex_to_rgb(hex_color):
        hex_color = hex_color.replace("#", "")
        return tuple(int(hex_color[i:i+2], 16)/255 for i in (0, 2, 4))

    for r, row in enumerate(display_table, start=1):
        for c, cell in enumerate(row["cells"], start=2):

            color = cell.get("color")

            if color:
                try:
                    rgb = hex_to_rgb(color)
                    style.append(
                        ("BACKGROUND", (c, r), (c, r), colors.Color(*rgb))
                    )
                except:
                    pass

    table.setStyle(TableStyle(style))

    elements.append(table)
    doc.build(elements)

    return response



from openpyxl import Workbook
from openpyxl.styles import PatternFill, Font, Alignment
from django.http import HttpResponse


def timetable_excel(request):

    school = request.user.profile.school
    display_table = request.session.get("display_table", [])

    wb = Workbook()
    ws = wb.active
    ws.title = "Timetable"

    # ----------------------------------
    # HEADER
    # ----------------------------------
    ws.append([f"{school.school_code} - {school.name}"])
    ws.append([f"{school.region} | {school.district} | {school.ward}"])
    ws.append([])

    ws["A1"].font = Font(bold=True, size=14)
    ws["A2"].font = Font(italic=True)

    headers = [
        "Day","Stream",
        "P1","P2","P3","P4","BREAK",
        "P6","P7","P8","P9","LUNCH",
        "P11","P12"
    ]

    ws.append(headers)

    header_row = ws.max_row
    for col in range(1, len(headers)+1):
        ws.cell(row=header_row, column=col).font = Font(bold=True)
        ws.cell(row=header_row, column=col).alignment = Alignment(horizontal="center")

    # ----------------------------------
    # BODY WITH COLORS
    # ----------------------------------
    row_index = ws.max_row + 1

    for row in display_table:

        values = [row["day"], row["stream"]] + [c["name"] or "-" for c in row["cells"]]
        ws.append(values)

        col_index = 3  # first period column

        for cell in row["cells"]:
            excel_cell = ws.cell(row=row_index, column=col_index)

            excel_cell.alignment = Alignment(horizontal="center", vertical="center")

            color = cell.get("color")

            if color:
                excel_color = color.replace("#", "")
                excel_cell.fill = PatternFill(
                    start_color=excel_color,
                    end_color=excel_color,
                    fill_type="solid"
                )

            col_index += 1

        row_index += 1

    # ----------------------------------
    # AUTO COLUMN WIDTH
    # ----------------------------------
    for col in ws.columns:
        max_len = 0
        col_letter = col[0].column_letter
        for cell in col:
            if cell.value:
                max_len = max(max_len, len(str(cell.value)))
        ws.column_dimensions[col_letter].width = max_len + 2

    # ----------------------------------
    # RESPONSE
    # ----------------------------------
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="{school.school_code}_TIMETABLE.xlsx"'
    )

    wb.save(response)
    return response








@login_required
# def school_summary_pdf(request):
#     school_code = request.GET.get("school")
#     year = request.GET.get("year")

#     context = build_school_summary_context(school_code, year)


@login_required
def school_summary_print(request):

    school_code  = request.GET.get("school")
    year         = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    if not all([school_code, year, exam_type_id]):
        return HttpResponse("Missing parameters", status=400)

    context = build_school_summary_context(
        school_code,
        year,
        exam_type_id
    )

    return render(
        request,
        "exams/reports/school_summary_print.html",
        context
    )


@login_required
def school_trend_print(request):

    school_code  = request.GET.get("school")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    if not all([school_code, start_year, end_year, exam_type_id]):
        return HttpResponse("Missing parameters", status=400)

    rows = compute_school_trend(
        school_code,
        start_year,
        end_year,
        exam_type_id   # ✅ FIX
    )

    school = School.objects.get(school_code=school_code)
    exam_type = ExamType.objects.get(id=exam_type_id)

    return render(
        request,
        "exams/reports/school_trend_print.html",
        {
            "school": school,
            "rows": rows,
            "start": int(start_year),
            "end": int(end_year),
            "exam_type": exam_type.name,   # ✅ THIS LINE FIXES IT
            "exam_type_id": int(exam_type_id),
        }
    )





@login_required
def subject_district_rank_html(request):

    region = request.GET.get("region")
    exam_type_id = request.GET.get("exam_type_id")
    year = request.GET.get("year")
    subject = request.GET.get("subject")

    rows = compute_subject_district_rank(region, exam_type_id, year, subject)

    return render(request,"exams/reports/subject_district_rank_html.html",{
        "rows": rows,
        "region": Region.objects.get(id=region),
        "exam_type": ExamType.objects.get(id=exam_type_id),
        "year": year,
        "subject": ExamSubject.objects.get(subject_code=subject)
    })




@login_required
def subject_district_rank_excel(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")

    rows = compute_subject_district_rank(region_id, exam_type_id, year, subject_code)

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)

    wb = Workbook()
    ws = wb.active
    ws.title = "Subject District Rank"

    # ---------------- HEADER ----------------
    ws.merge_cells("A1:L1")
    ws["A1"] = f"SUBJECT DISTRICT RANKING – {subject.subject_name_eng} – {exam_type.name} – {region.name} – {year}"
    ws["A1"].font = Font(bold=True, size=14)
    ws["A1"].alignment = Alignment(horizontal="center")

    ws.append([])

    # ---------------- TABLE HEADER ----------------
    headers = [
        "#","DISTRICT","SAT",
        "A(F)","A(M)","A(T)",
        "B(F)","B(M)","B(T)",
        "C(F)","C(M)","C(T)",
        "D(F)","D(M)","D(T)",
        "F(F)","F(M)","F(T)",
        "PASS","PASS %","GPA","GRADE","STATUS"
    ]

    ws.append(headers)

    for c in range(1, len(headers)+1):
        ws.cell(row=3, column=c).font = Font(bold=True)
        ws.cell(row=3, column=c).alignment = Alignment(horizontal="center")

    # ---------------- DATA ----------------
    r = 4
    for row in rows:
        ws.append([
            row["rank"],
            row["district"],
            row["sat"],

            row["af"],row["am"],row["at"],
            row["bf"],row["bm"],row["bt"],
            row["cf"],row["cm"],row["ct"],
            row["df"],row["dm"],row["dt"],
            row["ff"],row["fm"],row["ft"],

            row["pass_no"],
            row["pass_pct"],
            row["gpa"],
            row["grade"],
            row["status"]
        ])

        for c in range(1, 24):
            ws.cell(row=r, column=c).alignment = Alignment(horizontal="center")

        r += 1

    # ---------------- AUTOSIZE ----------------
    for i, col in enumerate(ws.iter_cols(min_row=3, max_row=ws.max_row), start=1):
        ws.column_dimensions[get_column_letter(i)].width = 15


    # ---------------- RESPONSE ----------------
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )

    filename = f"Subject_District_Rank_{subject.subject_name_eng}_{region.name}_{year}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response


@login_required
def subject_reg_school_rank_html(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # <-- MUST be subject_code

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    # Resolve subject ONLY by subject_code
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        raise Http404("Subject not found")

    rows = compute_subject_school_rank(
        region_id,
        exam_type_id,
        year,
        subject_code      # <-- pass code directly
    )

    return render(request, "exams/reports/subject_reg_school_rank_html.html", {
        "rows": rows,
        "region": region,
        "exam_type": exam_type,
        "subject": subject,
        "year": year,
        "exam_type_id": exam_type_id,
    })




@login_required
def subject_reg_school_rank_ext_html(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # <-- MUST be subject_code

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    # Resolve subject ONLY by subject_code
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        raise Http404("Subject not found")

    rows = compute_subject_school_rank_ext(
        region_id,
        exam_type_id,
        year,
        subject_code      # <-- pass code directly
    )

    return render(request, "exams/reports/subject_reg_school_rank_ext_html.html", {
        "rows": rows,
        "region": region,
        "exam_type": exam_type,
        "subject": subject,
        "year": year,
        "exam_type_id": exam_type_id,
    })





@login_required
def subject_reg_school_rank_excel(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # MUST be subject_code

    # --- Resolve subject ---
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        return HttpResponse("Subject not found")

    # --- Get rows using SAME logic as HTML ---
    rows = compute_subject_school_rank(
        region_id,
        exam_type_id,
        year,
        subject_code
    )

    # DEBUG (optional)
    # print("ROWS COUNT:", len(rows))

    from openpyxl import Workbook
    wb = Workbook()
    ws = wb.active
    ws.title = "Subject School Rank"

    # ---- HEADER ----
    headers = [
        "#","CODE","SCHOOL","DISTRICT",
        "A","B","C","D","F",
        "PASS","PASS %","GPA","GRADE","STATUS"
    ]
    ws.append(headers)

    # ---- DATA ----
    for r in rows:
        ws.append([
            r.get("rank"),
            r.get("code"),
            r.get("name"),
            r.get("district"),

            r.get("a"),
            r.get("b"),
            r.get("c"),
            r.get("d"),
            r.get("f"),

            r.get("pass"),
            r.get("pass_pct"),
            r.get("gpa"),
            r.get("grade"),
            r.get("status"),
        ])

    # ---- RESPONSE ----
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename=Subject_School_Rank_{subject.subject_code}_{year}.xlsx'
    )

    wb.save(response)
    return response





@login_required
def subject_reg_school_rank_ext_excel(request):

    region_id = request.GET.get("region")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")

    region = Region.objects.get(id=region_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)

    rows = compute_subject_school_rank_ext(
        region_id, exam_type_id, year, subject_code
    )

    wb = Workbook()
    ws = wb.active
    ws.title = "Subject School Rank"

    # ---------- TITLE ----------
    ws.merge_cells("A1:AJ1")
    ws["A1"] = f"SUBJECT SCHOOL RANKING – {subject.subject_name_eng} | {exam_type.name} | {region.name} | {year}"
    ws["A1"].font = ws["A1"].font.copy(bold=True)

    # ---------- HEADERS ----------
    headers = [
        "#","CNO","SCHOOL","REGION","DISTRICT","OWNERSHIP",

        "REG F","REG M","REG T",
        "SAT F","SAT M","SAT T",

        "A F","A M","A T","A %",
        "B F","B M","B T","B %",
        "C F","C M","C T","C %",
        "D F","D M","D T","D %",
        "F F","F M","F T","F %",

        "PASS F","PASS M","PASS T","PASS %",

        "GPA","GRADE","STATUS","POS"
    ]

    ws.append(headers)

    for cell in ws[2]:
        cell.font = cell.font.copy(bold=True)

    # ---------- DATA ----------
    for r in rows:
        ws.append([
            r["rank"], r["code"], r["name"], r["region"], r["district"], r["ownership"],

            r["reg_f"], r["reg_m"], r["reg_t"],
            r["sat_f"], r["sat_m"], r["sat_t"],

            r["af"], r["am"], r["at"], r["a_pct"],
            r["bf"], r["bm"], r["bt"], r["b_pct"],
            r["cf"], r["cm"], r["ct"], r["c_pct"],
            r["df"], r["dm"], r["dt"], r["d_pct"],
            r["ff"], r["fm"], r["ft"], r["f_pct"],

            r["pass_f"], r["pass_m"], r["pass_t"], r["pass_pct"],

            r["gpa"], r["grade"], r["status"], r["rank"]
        ])

    # ---------- RESPONSE ----------
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    filename = f"Subject_School_Rank_{subject.subject_code}_{year}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response

















#################################

#  SCHOOL RANKING - DISTRICT


##################################






@login_required
def district_school_ranking_html(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank(district, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/district_school_rank_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "district": District.objects.get(id=district)
    })





@login_required
def district_school_ranking_print(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank(district, year, exam_type_id)

    return render(request,"exams/reports/district_school_rank_print.html",{
        "rows": rows,
        "year": year,
        "exam_type": ExamType.objects.get(id=exam_type_id),
        "district": District.objects.get(id=district),
    })


@login_required
def district_school_ranking_excel(request):

    from openpyxl import Workbook
    from django.http import HttpResponse

    rows = compute_district_school_rank(
        request.GET["district"],
        request.GET["year"],
        request.GET["exam_type_id"]
    )

    wb=Workbook()
    ws=wb.active

    ws.append(["#","CODE","NAME","DISTRICT","REGION",
               "I","II","III","IV","0","#I-IV","%I-IV","GPA"])

    for r in rows:
        ws.append([
            r["rank"],r["code"],r["name"],r["district"],r["district"],
            r["d1"],r["d2"],r["d3"],r["d4"],r["d0"],
            r["pass"],r["pass_pct"],r["gpa"]
        ])

    resp=HttpResponse(content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
    resp["Content-Disposition"]="attachment; filename=District_School_Rank.xlsx"
    wb.save(resp)
    return resp


@login_required
def district_school_ranking_pdf(request):

    rows = compute_district_school_rank(
        request.GET["district"],
        request.GET["year"],
        request.GET["exam_type_id"]
    )

    from reportlab.platypus import SimpleDocTemplate,Table,TableStyle
    from reportlab.lib.pagesizes import A4
    from reportlab.lib import colors
    from django.http import HttpResponse

    resp=HttpResponse(content_type="application/pdf")
    resp["Content-Disposition"]="attachment; filename=District_School_Rank.pdf"

    data=[["#","CODE","NAME","DISTRICT","REGION","I","II","III","IV","0","#I-IV","%I-IV","GPA"]]

    for r in rows:
        data.append([
            r["rank"],r["code"],r["name"],r["district"],r["district"],
            r["d1"],r["d2"],r["d3"],r["d4"],r["d0"],
            r["pass"],r["pass_pct"],r["gpa"]
        ])

    table=Table(data,repeatRows=1)
    table.setStyle(TableStyle([
        ("GRID",(0,0),(-1,-1),1,colors.black),
        ("BACKGROUND",(0,0),(-1,0),colors.lightgrey),
        ("ALIGN",(0,0),(-1,-1),"CENTER")
    ]))

    doc=SimpleDocTemplate(resp,pagesize=A4)
    doc.build([table])
    return resp


@login_required
def district_school_ranking_ext_html(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank_ext(district, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/district_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "district": District.objects.get(id=district)
    })






@login_required
def district_school_ranking_ext_html(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank_ext(district, year, exam_type_id)

    exam_type = None
    if exam_type_id:
        exam_type = ExamType.objects.filter(id=exam_type_id).first()

    return render(request,"exams/reports/district_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "exam_type": exam_type,
        "district": District.objects.get(id=district)
    })


    
from openpyxl import Workbook
from django.http import HttpResponse

@login_required
def district_school_ranking_ext_excel(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank_ext(district, year, exam_type_id)

    wb = Workbook()
    ws = wb.active
    ws.title = "District Ranking"

    # ---------------- HEADER ROW ----------------
    headers = [
        "#","CNO","SCHOOL NAME","REGION","DISTRICT","OWNERSHIP",

        "REG F","REG M","REG T",

        "SAT F","SAT M","SAT T",

        "I F","I M","I T","I %",

        "II F","II M","II T","II %",

        "III F","III M","III T","III %",

        "PASS I-III F","PASS I-III M","PASS I-III T","PASS I-III %",

        "IV F","IV M","IV T","IV %",

        "FAIL F","FAIL M","FAIL T","FAIL %",

        "PASS I-IV F","PASS I-IV M","PASS I-IV T","PASS I-IV %",

        "GPA","GRADE","STATUS","POSITION"
    ]

    ws.append(headers)

    # ---------------- DATA ROWS ----------------
    for r in rows:
        ws.append([
            r["no"], r["cno"], r["name"], r["region"], r["district"], r["ownership"],

            r["reg_f"], r["reg_m"], r["reg_t"],

            r["sat_f"], r["sat_m"], r["sat_t"],

            r["i_f"], r["i_m"], r["i_t"], r["i_pct"],

            r["ii_f"], r["ii_m"], r["ii_t"], r["ii_pct"],

            r["iii_f"], r["iii_m"], r["iii_t"], r["iii_pct"],

            r["pass_i_iii_f"], r["pass_i_iii_m"], r["pass_i_iii_t"], r["pass_i_iii_pct"],

            r["iv_f"], r["iv_m"], r["iv_t"], r["iv_pct"],

            r["f_f"], r["f_m"], r["f_t"], r["f_pct"],

            r["pass_i_iv_f"], r["pass_i_iv_m"], r["pass_i_iv_t"], r["pass_i_iv_pct"],

            r["gpa"], r["grade"], r["status"], r["position"]
        ])

    # ---------------- RESPONSE ----------------
    filename = f"DISTRICT_SCHOOL_RANK_EXT_{year}.xlsx"

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response


@login_required
def district_school_ranking_ext_print(request):

    district = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_school_rank_ext(district, year, exam_type_id)

    return render(request,"exams/reports/district_school_rank_ext_html.html",{
        "rows": rows,
        "year": year,
        "exam_type_id": exam_type_id,
        "district": District.objects.get(id=district)
    })






@login_required
def subject_dis_school_rank_html(request):

    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # <-- MUST be subject_code

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    # Resolve subject ONLY by subject_code
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        raise Http404("Subject not found")

    rows = compute_subject_dis_school_rank(
        district_id,
        exam_type_id,
        year,
        subject_code      # <-- pass code directly
    )

    return render(request, "exams/reports/subject_dis_school_rank_html.html", {
        "rows": rows,
        "district": district,
        "exam_type": exam_type,
        "subject": subject,
        "year": year,
        "exam_type_id": exam_type_id,
    })




@login_required
def subject_dis_school_rank_ext_html(request):

    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # <-- MUST be subject_code

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)

    # Resolve subject ONLY by subject_code
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        raise Http404("Subject not found")

    rows = compute_subject_dis_school_rank_ext(
        district_id,
        exam_type_id,
        year,
        subject_code      # <-- pass code directly
    )

    return render(request, "exams/reports/subject_dis_school_rank_ext_html.html", {
        "rows": rows,
        "district": district,
        "exam_type": exam_type,
        "subject": subject,
        "year": year,
        "exam_type_id": exam_type_id,
    })





@login_required
def subject_dis_school_rank_excel(request):

    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")   # MUST be subject_code

    # --- Resolve subject ---
    subject = ExamSubject.objects.filter(subject_code=subject_code).first()
    if not subject:
        return HttpResponse("Subject not found")

    # --- Get rows using SAME logic as HTML ---
    rows = compute_subject_dis_school_rank(
        district_id,
        exam_type_id,
        year,
        subject_code
    )

    # DEBUG (optional)
    # print("ROWS COUNT:", len(rows))

    from openpyxl import Workbook
    wb = Workbook()
    ws = wb.active
    ws.title = "Subject School Rank"

    # ---- HEADER ----
    headers = [
        "#","CODE","SCHOOL","DISTRICT",
        "A","B","C","D","F",
        "PASS","PASS %","GPA","GRADE","STATUS"
    ]
    ws.append(headers)

    # ---- DATA ----
    for r in rows:
        ws.append([
            r.get("rank"),
            r.get("code"),
            r.get("name"),
            r.get("district"),

            r.get("a"),
            r.get("b"),
            r.get("c"),
            r.get("d"),
            r.get("f"),

            r.get("pass"),
            r.get("pass_pct"),
            r.get("gpa"),
            r.get("grade"),
            r.get("status"),
        ])

    # ---- RESPONSE ----
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename=Subject_School_Rank_{subject.subject_code}_{year}.xlsx'
    )

    wb.save(response)
    return response





@login_required
def subject_dis_school_rank_ext_excel(request):

    district_id = request.GET.get("district")
    year = request.GET.get("year")
    exam_type_id = request.GET.get("exam_type_id")
    subject_code = request.GET.get("subject")

    district = District.objects.get(id=district_id)
    exam_type = ExamType.objects.get(id=exam_type_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)

    rows = compute_subject_dis_school_rank_ext(
        district_id, exam_type_id, year, subject_code
    )

    wb = Workbook()
    ws = wb.active
    ws.title = "Subject School Rank"

    # ---------- TITLE ----------
    ws.merge_cells("A1:AJ1")
    ws["A1"] = f"SUBJECT SCHOOL RANKING – {subject.subject_name_eng} | {exam_type.name} | {district.name} | {year}"
    ws["A1"].font = ws["A1"].font.copy(bold=True)

    # ---------- HEADERS ----------
    headers = [
        "#","CNO","SCHOOL","REGION","DISTRICT","OWNERSHIP",

        "REG F","REG M","REG T",
        "SAT F","SAT M","SAT T",

        "A F","A M","A T","A %",
        "B F","B M","B T","B %",
        "C F","C M","C T","C %",
        "D F","D M","D T","D %",
        "F F","F M","F T","F %",

        "PASS F","PASS M","PASS T","PASS %",

        "GPA","GRADE","STATUS","POS"
    ]

    ws.append(headers)

    for cell in ws[2]:
        cell.font = cell.font.copy(bold=True)

    # ---------- DATA ----------
    for r in rows:
        ws.append([
            r["rank"], r["code"], r["name"], r["district"], r["district"], r["ownership"],

            r["reg_f"], r["reg_m"], r["reg_t"],
            r["sat_f"], r["sat_m"], r["sat_t"],

            r["af"], r["am"], r["at"], r["a_pct"],
            r["bf"], r["bm"], r["bt"], r["b_pct"],
            r["cf"], r["cm"], r["ct"], r["c_pct"],
            r["df"], r["dm"], r["dt"], r["d_pct"],
            r["ff"], r["fm"], r["ft"], r["f_pct"],

            r["pass_f"], r["pass_m"], r["pass_t"], r["pass_pct"],

            r["gpa"], r["grade"], r["status"], r["rank"]
        ])

    # ---------- RESPONSE ----------
    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    filename = f"Subject_School_Rank_{subject.subject_code}_{year}.xlsx"
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response



@login_required
def school_subject_trend_html(request):

    school_code  = request.GET.get("school")
    subject_code = request.GET.get("subject")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_school_subject_trend(
        school_code,
        subject_code,
        start_year,
        end_year,
        exam_type_id
    )

    school = School.objects.get(school_code=school_code)

    subject = ExamSubject.objects.get(subject_code=subject_code)

    return render(request, "exams/reports/school_subject_trend_html.html", {
        "school": school,
        "subject": subject,                 # ✅ FULL OBJECT
        "rows": rows,
        "start": int(start_year),
        "end": int(end_year),
        "exam_type_id": int(exam_type_id),
    })



@login_required
def region_subject_trend_html(request):

    region_id    = request.GET.get("region")
    subject_code = request.GET.get("subject")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_region_subject_trend(
        region_id,
        subject_code,
        start_year,
        end_year,
        exam_type_id
    )

    region = Region.objects.get(id=region_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)
    exam    = ExamType.objects.get(id=exam_type_id)   # ✅ ADD THIS
    return render(request, "exams/reports/region_subject_trend_html.html", {
        "region": region,
        "subject": subject,
        "rows": rows,
        "start": int(start_year),
        "end": int(end_year),
        "exam_type_id": int(exam_type_id),
        "exam": exam,   # ✅ PASS TO TEMPLATE
    })



@login_required
def district_subject_trend_html(request):

    district_id  = request.GET.get("district")
    subject_code = request.GET.get("subject")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_district_subject_trend(
        district_id,
        subject_code,
        start_year,
        end_year,
        exam_type_id
    )

    district = District.objects.get(id=district_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)
    exam    = ExamType.objects.get(id=exam_type_id)   # ✅ ADD THIS
    return render(request, "exams/reports/district_subject_trend_html.html", {
        "district": district,
        "subject": subject,
        "rows": rows,
        "start": int(start_year),
        "end": int(end_year),
        "exam_type_id": int(exam_type_id),
        "exam": exam,   # ✅ PASS TO TEMPLATE
    })



@login_required
def school_subject_trend_print(request):

    school_code  = request.GET.get("school")
    subject_code = request.GET.get("subject")
    start_year   = request.GET.get("start")
    end_year     = request.GET.get("end")
    exam_type_id = request.GET.get("exam_type_id")

    rows = compute_school_subject_trend(
        school_code,
        subject_code,
        start_year,
        end_year,
        exam_type_id
    )

    school = School.objects.get(school_code=school_code)
    subject = ExamSubject.objects.get(subject_code=subject_code)
    exam    = ExamType.objects.get(id=exam_type_id)   # ✅ ADD THIS

    return render(
        request,
        "exams/reports/school_subject_trend_print.html",
        {
            "school": school,
            "subject": subject,
            "rows": rows,
            "start": int(start_year),
            "end": int(end_year),
        }
    )


@login_required
def district_subject_trend_print(request):

    district_id  = int(request.GET.get("district"))
    subject_code = request.GET.get("subject")
    start_year   = int(request.GET.get("start"))
    end_year     = int(request.GET.get("end"))
    exam_type_id = int(request.GET.get("exam_type_id"))

    rows = compute_district_subject_trend(
        district_id, subject_code, start_year, end_year, exam_type_id
    )

    district = District.objects.get(id=district_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)
    exam    = ExamType.objects.get(id=exam_type_id)   # ✅ ADD THIS

    return render(request,
        "exams/reports/_subject_trend_print_base.html",
        {
            "title": "District Subject Trend Print",
            "header_name": district.name,
            "subject": subject,
            "exam": exam,                 # ✅ ADD THIS
            "rows": rows,
            "start": start_year,
            "end": end_year,
            "location_line": (
                f"District: {district.name} | "
                f"Region: {district.region.name}"
            )
        }
    )


@login_required
def region_subject_trend_print(request):

    region_id    = int(request.GET.get("region"))
    subject_code = request.GET.get("subject")
    start_year   = int(request.GET.get("start"))
    end_year     = int(request.GET.get("end"))
    exam_type_id = int(request.GET.get("exam_type_id"))

    rows = compute_region_subject_trend(
        region_id, subject_code, start_year, end_year, exam_type_id
    )

    region  = Region.objects.get(id=region_id)
    subject = ExamSubject.objects.get(subject_code=subject_code)
    exam    = ExamType.objects.get(id=exam_type_id)

    return render(
        request,
        "exams/reports/_subject_trend_print_base.html",
        {
            "title": "Region Subject Trend Print",
            "header_name": region.name,
            "subject": subject,
            "exam": exam,                 # ✅ ADD THIS
            "rows": rows,
            "start": start_year,
            "end": end_year,
            "location_line": f"Region: {region.name}",
        }
    )



































































from django.urls import reverse_lazy
from django.views.generic import CreateView, ListView
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import (
    AcademicLevel,
    JointExamProject,
    GradingRule,
    DivisionRule,
    ExamScore
)
from .forms import (
    AcademicLevelForm,
    JointExamProjectForm,
    GradingRuleForm,
    DivisionRuleForm,
    ExamScoreForm
)


# ===============================
# Academic Level
# ===============================

class AcademicLevelCreateView(LoginRequiredMixin, CreateView):
    model = AcademicLevel
    form_class = AcademicLevelForm
    template_name = 'exams/academic_level_form.html'
    success_url = reverse_lazy('academic_level_list')


class AcademicLevelListView(LoginRequiredMixin, ListView):
    model = AcademicLevel
    template_name = 'exams/academic_level_list.html'


# ===============================
# Joint Exam Project
# ===============================

class JointExamProjectCreateView(LoginRequiredMixin, CreateView):
    model = JointExamProject
    form_class = JointExamProjectForm
    template_name = 'exams/project_form.html'
    success_url = reverse_lazy('project_list')


class JointExamProjectListView(LoginRequiredMixin, ListView):
    model = JointExamProject
    template_name = 'exams/project_list.html'


# ===============================
# Grading Rule
# ===============================

class GradingRuleCreateView(LoginRequiredMixin, CreateView):
    model = GradingRule
    form_class = GradingRuleForm
    template_name = 'exams/grading_rule_form.html'
    success_url = reverse_lazy('grading_rule_list')


class GradingRuleListView(LoginRequiredMixin, ListView):
    model = GradingRule
    template_name = 'exams/grading_rule_list.html'


# ===============================
# Division Rule
# ===============================

class DivisionRuleCreateView(LoginRequiredMixin, CreateView):
    model = DivisionRule
    form_class = DivisionRuleForm
    template_name = 'exams/division_rule_form.html'
    success_url = reverse_lazy('division_rule_list')


class DivisionRuleListView(LoginRequiredMixin, ListView):
    model = DivisionRule
    template_name = 'exams/division_rule_list.html'


# ===============================
# Exam Score Entry
# ===============================

class ExamScoreCreateView(LoginRequiredMixin, CreateView):
    model = ExamScore
    form_class = ExamScoreForm
    template_name = 'exams/score_form.html'
    success_url = reverse_lazy('score_list')

    def form_valid(self, form):
        form.instance.entered_by = self.request.user
        return super().form_valid(form)


class ExamScoreListView(LoginRequiredMixin, ListView):
    model = ExamScore
    template_name = 'exams/score_list.html'





from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from .forms import AcademicLevelForm
from .models import AcademicLevel


@login_required
def academic_level_add(request):
    if request.method == 'POST':
        form = AcademicLevelForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('academic_level_list')
    else:
        form = AcademicLevelForm()

    return render(request, 'exams/academic_level_form.html', {
        'form': form
    })


@login_required
def academic_level_list(request):
    levels = AcademicLevel.objects.all()
    return render(request, 'exams/academic_level_list.html', {
        'levels': levels
    })



from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from .models import JointExamProject
from .forms import JointExamProjectForm


@login_required
def project_list(request):
    projects = JointExamProject.objects.select_related("academic_level")
    return render(request, "exams/project_list.html", {
        "projects": projects
    })


@login_required
def project_add(request):
    if request.method == "POST":
        form = JointExamProjectForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("exams:project_list")
    else:
        form = JointExamProjectForm()

    return render(request, "exams/project_form.html", {
        "form": form
    })



@login_required
def academic_level_list(request):
    levels = AcademicLevel.objects.all()
    return render(request, "exams/academic_level_list.html", {"levels": levels})


@login_required
def academic_level_add(request):
    if request.method == "POST":
        form = AcademicLevelForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("exams:academic_level_list")
    else:
        form = AcademicLevelForm()

    return render(request, "exams/academic_level_form.html", {"form": form})


@login_required
def project_list(request):
    projects = JointExamProject.objects.select_related("academic_level")
    return render(request, "exams/project_list.html", {"projects": projects})


@login_required
def project_add(request):
    if request.method == "POST":
        form = JointExamProjectForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("exams:project_list")
    else:
        form = JointExamProjectForm()

    return render(request, "exams/project_form.html", {"form": form})


@login_required
def grading_rule_list(request):
    rules = GradingRule.objects.select_related("academic_level")
    return render(request, "exams/grading_rule_list.html", {"rules": rules})


@login_required
def grading_rule_add(request):
    if request.method == "POST":
        form = GradingRuleForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("exams:grading_rule_list")
    else:
        form = GradingRuleForm()

    return render(request, "exams/grading_rule_form.html", {"form": form})


@login_required
def division_rule_list(request):
    rules = DivisionRule.objects.select_related("academic_level")
    return render(request, "exams/division_rule_list.html", {"rules": rules})


@login_required
def division_rule_add(request):
    if request.method == "POST":
        form = DivisionRuleForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect("exams:division_rule_list")
    else:
        form = DivisionRuleForm()

    return render(request, "exams/division_rule_form.html", {"form": form})


@login_required
def score_list(request):
    scores = ExamScore.objects.select_related(
        "project", "subject", "school"
    )
    return render(request, "exams/score_list.html", {"scores": scores})


@login_required
def score_add(request):
    if request.method == "POST":
        form = ExamScoreForm(request.POST)
        if form.is_valid():
            obj = form.save(commit=False)
            obj.entered_by = request.user
            obj.save()
            return redirect("exams:score_list")
    else:
        form = ExamScoreForm()

    return render(request, "exams/score_form.html", {"form": form})



@login_required
def project_process(request, pk):
    process_project_results(pk)
    messages.success(request, "Project processed successfully.")
    return redirect("exams:project_list")



from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from django.core.paginator import Paginator
from django.contrib.auth.decorators import login_required
from django.db import transaction
import openpyxl

@login_required
@transaction.atomic
def project_students_upload(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    students_qs = (
        ProjectStudent.objects
        .filter(project=project)
        .select_related("school")
        .only("id", "student_number", "full_name", "sex", "school__school_code")
    )

    paginator = Paginator(students_qs, 50)
    page_number = request.GET.get("page")
    students = paginator.get_page(page_number)

    # ======================================================
    # UPLOAD HANDLER
    # ======================================================
    if request.method == "POST" and request.FILES.get("file"):

        excel_file = request.FILES["file"]

        try:
            wb = openpyxl.load_workbook(excel_file, data_only=True)
            ws = wb.active
        except Exception:
            return JsonResponse({
                "status": "error",
                "message": "Invalid Excel file"
            })

        # --------------------------------------------------
        # READ HEADER
        # --------------------------------------------------
        header_row = [str(c.value).strip().lower() for c in ws[1]]

        required_columns = {
            "school_code",
            "student_number",
            "full_name",
            "sex",
            "combination",
        }

        if not required_columns.issubset(set(header_row)):
            return JsonResponse({
                "status": "error",
                "message": "Invalid Excel format"
            })

        col_index = {h: i for i, h in enumerate(header_row)}

        # --------------------------------------------------
        # FAST LOOKUP MAPS
        # --------------------------------------------------

        # 1️⃣ School map
        schools_map = {
            s.school_code: s.id
            for s in School.objects.only("id", "school_code")
        }

        # 2️⃣ Combination map (per academic level)
        combinations = (
            ExamCombination.objects
            .filter(academic_level=project.academic_level)
            .prefetch_related("papers")
        )

        combo_map = {
            c.code: {
                "id": c.id,
                "papers": list(c.papers.values_list("paper_id", flat=True))
            }
            for c in combinations
        }

        # 3️⃣ Existing students map
        existing_students = dict(
            ProjectStudent.objects
            .filter(project=project)
            .values_list("student_number", "id")
        )

        students_to_insert = []
        scores_to_insert = []

        # ======================================================
        # PASS 1: CREATE STUDENTS
        # ======================================================
        for row in ws.iter_rows(min_row=2, values_only=True):

            school_code = str(row[col_index["school_code"]]).strip()
            student_number = str(row[col_index["student_number"]]).strip()
            name = str(row[col_index["full_name"]]).strip()
            sex = str(row[col_index["sex"]]).strip()
            combo_code = str(row[col_index["combination"]]).strip()

            school_id = schools_map.get(school_code)
            combo_data = combo_map.get(combo_code)

            if not school_id or not combo_data:
                continue

            if student_number not in existing_students:
                students_to_insert.append(
                    ProjectStudent(
                        project=project,
                        school_id=school_id,
                        student_number=student_number,
                        full_name=name,
                        sex=sex,
                        combination_id=combo_data["id"],
                    )
                )

        with transaction.atomic():
            created_students = ProjectStudent.objects.bulk_create(
                students_to_insert,
                ignore_conflicts=True,
                batch_size=2000,
            )

        # --------------------------------------------------
        # REFRESH STUDENT MAP (INCLUDE NEW)
        # --------------------------------------------------
        all_students = dict(
            ProjectStudent.objects
            .filter(project=project)
            .values_list("student_number", "id")
        )

        # ======================================================
        # PASS 2: CREATE EXAM SCRIPTS
        # ======================================================
        for row in ws.iter_rows(min_row=2, values_only=True):

            student_number = str(row[col_index["student_number"]]).strip()
            combo_code = str(row[col_index["combination"]]).strip()

            student_id = all_students.get(student_number)
            combo_data = combo_map.get(combo_code)

            if not student_id or not combo_data:
                continue

            for paper_id in combo_data["papers"]:
                scores_to_insert.append(
                    ExamScore(
                        project_id=project_id,
                        student_id=student_id,
                        paper_id=paper_id,
                    )
                )

        with transaction.atomic():
            ExamScore.objects.bulk_create(
                scores_to_insert,
                ignore_conflicts=True,
                batch_size=5000,
            )

        return JsonResponse({
            "status": "success",
            "students_created": len(created_students),
            "scripts_created": len(scores_to_insert),
        })

    # ======================================================
    # PAGE LOAD
    # ======================================================
    return render(request, "exams/project_students_upload.html", {
        "project": project,
        "students": students,
    })







from django.contrib import messages
from django.shortcuts import get_object_or_404

@login_required
def project_process(request, pk):
    project = get_object_or_404(JointExamProject, pk=pk)

    # TODO: call processing engine here later
    messages.success(request, "Project processed successfully.")

    return redirect("exams:project_list")


from django.http import HttpResponse
from openpyxl import Workbook
from openpyxl.styles import Font


@login_required
def project_students_sample_excel(request, project_id):
    project = JointExamProject.objects.get(id=project_id)

    wb = Workbook()
    ws = wb.active
    ws.title = "Student Sample"


    # Headers
    headers = ["school_code", "student_number", "full_name", "sex"]

    # If A-Level include combination
    if project.academic_level.code.upper() == "A-LEVEL":
        headers.append("combination")

    ws.append(headers)

    # Make header bold
    for col in range(1, len(headers) + 1):
        ws.cell(row=1, column=col).font = Font(bold=True)

    # Example row
    example = ["S0001", "S0001-1001", "John Doe", "M"]

    if project.academic_level.code.upper() == "A-LEVEL":
        example.append("PCM")

    ws.append(example)

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="student_sample_{project.id}.xlsx"'

    wb.save(response)
    return response



@transaction.atomic
def bulk_register_student_papers(project, dataframe):

    # PRELOAD (FAST)
    students_map = {
        s.student_number: s.id
        for s in ProjectStudent.objects.filter(project=project)
        .only("id", "student_number")
    }

    papers_map = {
        p.paper_code: p.id
        for p in ExamSubjectPaper.objects.all()
        .only("id", "paper_code")
    }

    insert_list = []

    for _, row in dataframe.iterrows():

        student_id = students_map.get(str(row["student_number"]).strip())
        if not student_id:
            continue

        # Loop through all columns except first
        for col in dataframe.columns[1:]:

            paper_code = str(row[col]).strip()

            if paper_code and paper_code != "nan":

                paper_id = papers_map.get(paper_code)
                if not paper_id:
                    continue

                insert_list.append(
                    ExamScore(
                        project=project,
                        student_id=student_id,
                        paper_id=paper_id,
                        score=None
                    )
                )

    ExamScore.objects.bulk_create(
        insert_list,
        ignore_conflicts=True,
        batch_size=2000
    )





@login_required
def assign_student_subjects(request, project_id, student_id):
    project = JointExamProject.objects.get(id=project_id)
    student = ProjectStudent.objects.get(id=student_id)

    subjects = ExamSubject.objects.filter(
        academic_level=project.academic_level
    )

    if request.method == "POST":
        selected = request.POST.getlist("subjects")

        StudentSubject.objects.filter(student=student).delete()

        for sub_id in selected:
            StudentSubject.objects.create(
                project=project,
                student=student,
                subject_id=sub_id
            )

        return redirect("exams:project_students_upload", project_id=project.id)

    return render(request, "exams/assign_subjects.html", {
        "project": project,
        "student": student,
        "subjects": subjects
    })


@login_required
def delete_project_student(request, project_id, student_id):
    ProjectStudent.objects.filter(id=student_id).delete()
    return redirect("exams:project_students_upload", project_id=project_id)



@login_required
def get_student_papers_modal(request, project_id, student_id):

    project = JointExamProject.objects.get(id=project_id)
    student = ProjectStudent.objects.get(id=student_id)

    papers = ExamSubjectPaper.objects.select_related("subject").order_by(
        "subject__subject_code", "paper_code"
    )

    assigned = set(
        ExamScore.objects.filter(
            project=project,
            student=student
        ).values_list("paper_id", flat=True)
    )

    return render(request, "exams/ajax_student_papers_modal.html", {
        "student": student,
        "papers": papers,
        "assigned": assigned
    })



from django.db import transaction
from django.http import JsonResponse


@login_required
@transaction.atomic
def assign_student_papers_ajax(request, project_id, student_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    project = JointExamProject.objects.get(id=project_id)
    student = ProjectStudent.objects.get(id=student_id)

    paper_ids = request.POST.getlist("papers[]")

    # Remove existing registrations
    ExamScore.objects.filter(
        project=project,
        student=student
    ).delete()

    insert_list = []

    for pid in paper_ids:
        insert_list.append(
            ExamScore(
                project=project,
                student=student,
                paper_id=pid,
                score=None
            )
        )

    ExamScore.objects.bulk_create(insert_list, batch_size=500)

    return JsonResponse({
        "status": "success",
        "count": len(insert_list)
    })




from openpyxl import Workbook
from django.http import HttpResponse

def subjectpaper_template_download(request):
    wb = Workbook()
    ws = wb.active
    ws.title = "Subject Papers"

    headers = ["subject_code", "paper_code", "paper_name", "max_marks"]
    ws.append(headers)

    ws.append(["031", "31_1", "Physics Theory", 100])
    ws.append(["031", "31_2", "Physics Practical", 50])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = "attachment; filename=subject_papers_template.xlsx"

    wb.save(response)
    return response


from django.contrib import messages
from django.shortcuts import redirect
from django.db import transaction
import openpyxl

@transaction.atomic
def subjectpaper_bulk_upload(request):

    if request.method != "POST":
        return redirect("exams:subjectpaper_list")

    file = request.FILES.get("excel_file")
    if not file:
        messages.error(request, "No file uploaded.")
        return redirect("exams:subjectpaper_list")

    # --------------------------------------------------
    # LOAD EXCEL
    # --------------------------------------------------
    try:
        wb = openpyxl.load_workbook(file, data_only=True)
        ws = wb.active
    except Exception:
        messages.error(request, "Invalid Excel file.")
        return redirect("exams:subjectpaper_list")

    # --------------------------------------------------
    # READ HEADER
    # --------------------------------------------------
    header = [str(c.value).strip().lower() for c in ws[1]]

    required_columns = {
        "subject_code",
        "paper_code",
        "paper_name",
        "max_marks",
    }

    if not required_columns.issubset(set(header)):
        messages.error(request, "Invalid Excel format.")
        return redirect("exams:subjectpaper_list")

    col = {h: i for i, h in enumerate(header)}

    # --------------------------------------------------
    # FAST SUBJECT MAP
    # --------------------------------------------------
    subjects_map = {
        s.subject_code: s.subject_id
        for s in ExamSubject.objects.only("subject_id", "subject_code")
    }

    insert_list = []
    skipped = 0

    # --------------------------------------------------
    # PROCESS ROWS
    # --------------------------------------------------
    for row in ws.iter_rows(min_row=2, values_only=True):

        subject_code = str(row[col["subject_code"]]).strip()
        subject_id = subjects_map.get(subject_code)

        if not subject_id:
            skipped += 1
            continue

        try:
            max_marks = float(row[col["max_marks"]])
        except (TypeError, ValueError):
            skipped += 1
            continue

        insert_list.append(
            ExamSubjectPaper(
                subject_id=subject_id,
                paper_code=str(row[col["paper_code"]]).strip(),
                paper_name=str(row[col["paper_name"]]).strip(),
                max_marks=max_marks,
            )
        )

    # --------------------------------------------------
    # BULK INSERT
    # --------------------------------------------------
    ExamSubjectPaper.objects.bulk_create(
        insert_list,
        ignore_conflicts=True,
        batch_size=1000,
    )

    messages.success(
        request,
        f"{len(insert_list)} papers uploaded successfully. {skipped} skipped."
    )

    return redirect("exams:subjectpaper_list")


@login_required
def subjectpaper_list(request):
    papers = ExamSubjectPaper.objects.select_related("subject").all()

    return render(request, "exams/subjectpaper_list.html", {
        "papers": papers
    })


@login_required
def get_bulk_papers_modal(request, project_id):

    project = JointExamProject.objects.get(id=project_id)

    papers = ExamSubjectPaper.objects.select_related("subject").order_by(
        "subject__subject_code", "paper_code"
    )

    return render(request, "exams/ajax_bulk_assign_modal.html", {
        "papers": papers
    })



from django.db import transaction
from django.http import JsonResponse


@login_required
@transaction.atomic
def bulk_assign_papers_ajax(request, project_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    project = JointExamProject.objects.get(id=project_id)

    student_ids = request.POST.getlist("students[]")
    paper_ids = request.POST.getlist("papers[]")

    # Remove existing for selected students
    ExamScore.objects.filter(
        project=project,
        student_id__in=student_ids
    ).delete()

    insert_list = []

    for sid in student_ids:
        for pid in paper_ids:
            insert_list.append(
                ExamScore(
                    project=project,
                    student_id=sid,
                    paper_id=pid,
                    score=None
                )
            )

    ExamScore.objects.bulk_create(
        insert_list,
        batch_size=2000
    )

    return JsonResponse({
        "status": "success",
        "students": len(student_ids),
        "papers": len(paper_ids)
    })






from django.db import transaction
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
import openpyxl


@login_required
@transaction.atomic
def bulk_student_paper_upload(request, project_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    project = get_object_or_404(JointExamProject, id=project_id)

    file = request.FILES.get("file")
    if not file:
        return JsonResponse({"status": "error", "message": "No file"})

    # --------------------------------------------------
    # LOAD EXCEL
    # --------------------------------------------------
    try:
        wb = openpyxl.load_workbook(file, data_only=True)
        ws = wb.active
    except Exception:
        return JsonResponse({
            "status": "error",
            "message": "Invalid Excel file"
        })

    # --------------------------------------------------
    # READ HEADER
    # --------------------------------------------------
    header = [str(c.value).strip().lower() for c in ws[1]]

    if "student_number" not in header:
        return JsonResponse({
            "status": "error",
            "message": "student_number column missing"
        })

    col = {h: i for i, h in enumerate(header)}

    # Paper columns = everything except student_number
    paper_columns = [h for h in header if h != "student_number"]

    # --------------------------------------------------
    # 1️⃣ BUILD FAST LOOKUP MAPS
    # --------------------------------------------------

    student_map = dict(
        ProjectStudent.objects
        .filter(project=project)
        .values_list("student_number", "id")
    )

    paper_map = dict(
        ExamSubjectPaper.objects
        .values_list("paper_code", "id")
    )

    # --------------------------------------------------
    # 2️⃣ PREPARE INSERT LIST
    # --------------------------------------------------
    insert_list = []
    skipped_students = 0
    skipped_papers = 0
    student_ids_in_file = set()

    for row in ws.iter_rows(min_row=2, values_only=True):

        student_number = str(row[col["student_number"]]).strip()
        student_id = student_map.get(student_number)

        if not student_id:
            skipped_students += 1
            continue

        student_ids_in_file.add(student_id)

        for paper_col in paper_columns:

            value = row[col[paper_col]]

            if value is None:
                continue

            paper_code = str(value).strip()
            paper_id = paper_map.get(paper_code)

            if not paper_id:
                skipped_papers += 1
                continue

            insert_list.append(
                ExamScore(
                    project_id=project.id,
                    student_id=student_id,
                    paper_id=paper_id,
                    score=None
                )
            )

    # --------------------------------------------------
    # 3️⃣ DELETE EXISTING SCORES (ONLY AFFECTED STUDENTS)
    # --------------------------------------------------
    if student_ids_in_file:
        ExamScore.objects.filter(
            project=project,
            student_id__in=student_ids_in_file
        ).delete()

    # --------------------------------------------------
    # 4️⃣ BULK INSERT
    # --------------------------------------------------
    ExamScore.objects.bulk_create(
        insert_list,
        batch_size=5000
    )

    return JsonResponse({
        "status": "success",
        "registered": len(insert_list),
        "skipped_students": skipped_students,
        "skipped_papers": skipped_papers
    })


@login_required
def project_papers_upload(request, project_id):

    project = JointExamProject.objects.get(id=project_id)

    students = ProjectStudent.objects.filter(
        project=project
    ).select_related("school").prefetch_related(
        "examscore_set__paper__subject"
    )

    return render(request, "exams/project_papers_upload.html", {
        "project": project,
        "students": students
    })



from django.core.paginator import Paginator
from django.db.models import Count, Q, F, FloatField, ExpressionWrapper
from django.db.models.functions import Coalesce

@login_required
def project_marks_subjects(request, project_id):

    project = JointExamProject.objects.get(id=project_id)

    search_query = request.GET.get("q", "").strip()

    papers = (
        ExamSubjectPaper.objects
        .filter(examscore__project_id=project_id)   # 🔥 only papers with data
        .select_related("subject")
        .distinct()
        .annotate(
            total_scripts=Count(
                "examscore",
                filter=Q(examscore__project_id=project_id),
                distinct=True
            ),
            filled_scores=Count(
                "examscore",
                filter=Q(
                    examscore__project_id=project_id,
                    examscore__score__isnull=False
                ),
                distinct=True
            )
        )
    )

    # 🔍 Search filter
    if search_query:
        papers = papers.filter(
            Q(subject__subject_name_eng__icontains=search_query) |
            Q(subject__subject_code__icontains=search_query) |
            Q(paper_code__icontains=search_query)
        )

    # 📊 Completion %
    papers = papers.annotate(
        completion_percent=ExpressionWrapper(
            100.0 * F("filled_scores") /
            Coalesce(F("total_scripts"), 1),
            output_field=FloatField()
        )
    ).order_by("subject__subject_code", "paper_code")

    # 📄 Pagination
    paginator = Paginator(papers, 25)
    page_number = request.GET.get("page")
    page_obj = paginator.get_page(page_number)

    return render(request, "exams/project_marks_subjects.html", {
        "project": project,
        "page_obj": page_obj,
        "search_query": search_query
    })





from django.core.paginator import Paginator
from django.db.models import Count, Q, F, FloatField, ExpressionWrapper
from django.db.models.functions import Coalesce

@login_required
def project_marks_schools(request, project_id, paper_id):

    project = JointExamProject.objects.get(id=project_id)
    paper = ExamSubjectPaper.objects.select_related("subject").get(id=paper_id)

    search_query = request.GET.get("q", "").strip()

    schools = (
        School.objects
        .filter(
            projectstudent__project_id=project_id,
            projectstudent__examscore__paper_id=paper_id
        )
        .distinct()
        .annotate(
            total_scripts=Count(
                "projectstudent__examscore",
                filter=Q(
                    projectstudent__project_id=project_id,
                    projectstudent__examscore__paper_id=paper_id
                ),
                distinct=True
            ),
            entered_marks=Count(
                "projectstudent__examscore",
                filter=Q(
                    projectstudent__project_id=project_id,
                    projectstudent__examscore__paper_id=paper_id,
                    projectstudent__examscore__score__isnull=False
                ),
                distinct=True
            )
        )
    )

    # 🔍 SEARCH
    if search_query:
        schools = schools.filter(
            Q(name__icontains=search_query) |
            Q(school_code__icontains=search_query)
        )

    # 📊 Progress %
    schools = schools.annotate(
        progress_percent=ExpressionWrapper(
            100.0 * F("entered_marks") /
            Coalesce(F("total_scripts"), 1),
            output_field=FloatField()
        )
    ).order_by("name")

    # 📄 PAGINATION
    paginator = Paginator(schools, 25)
    page_number = request.GET.get("page")
    page_obj = paginator.get_page(page_number)

    return render(request, "exams/project_marks_schools.html", {
        "project": project,
        "paper": paper,
        "page_obj": page_obj,
        "search_query": search_query
    })




@login_required
def project_marks_capture_page(request, project_id, paper_id, school_id):

    project = JointExamProject.objects.get(id=project_id)
    paper = ExamSubjectPaper.objects.get(id=paper_id)

    scores = ExamScore.objects.filter(
        project=project,
        paper=paper,
        student__school_id=school_id
    ).select_related("student")

    return render(request, "exams/project_marks_capture.html", {
        "project": project,
        "paper": paper,
        "scores": scores
    })



import json
from django.utils.timezone import now


@login_required
@transaction.atomic
def save_marks_ajax(request, project_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    data = json.loads(request.POST.get("data", "[]"))

    user = request.user

    update_ids = []

    for item in data:

        score_value = item.get("score", "").strip()

        if score_value == "":
            continue  # skip blank fields

        update_ids.append(item["id"])

        ExamScore.objects.filter(
            id=item["id"],
            project_id=project_id
        ).update(
            score=score_value,
            updated_by=user,
            score_status=1,
            updated_at=now()
        )

    return JsonResponse({
        "status": "success",
        "updated_count": len(update_ids)
    })




@login_required
@transaction.atomic
def bulk_register_by_combination(request, project_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    project = JointExamProject.objects.get(id=project_id)
    file = request.FILES.get("file")

    df = pd.read_excel(file)

    # -------------------------------------------------
    # 1️⃣ FAST LOOKUP MAPS
    # -------------------------------------------------

    # student_number → student_id
    student_map = dict(
        ProjectStudent.objects.filter(project=project)
        .values_list("student_number", "id")
    )

    # combination_code → combination_id
    combination_map = dict(
        ExamCombination.objects.values_list("code", "id")
    )

    # combination_id → list of paper_ids
    combo_papers = {}

    for combo_id, paper_id in CombinationPaper.objects.values_list(
        "combination_id", "paper_id"
    ):
        combo_papers.setdefault(combo_id, []).append(paper_id)

    insert_list = []
    skipped_students = 0
    skipped_combinations = 0

    for _, row in df.iterrows():

        student_number = str(row["student_number"]).strip()
        combo_code = str(row["combination"]).strip()

        student_id = student_map.get(student_number)
        if not student_id:
            skipped_students += 1
            continue

        combo_id = combination_map.get(combo_code)
        if not combo_id:
            skipped_combinations += 1
            continue

        paper_ids = combo_papers.get(combo_id, [])

        for paper_id in paper_ids:
            insert_list.append(
                ExamScore(
                    project_id=project.id,
                    student_id=student_id,
                    paper_id=paper_id,
                    score=None
                )
            )

    # Delete only affected students
    affected_student_ids = list(
        set([i.student_id for i in insert_list])
    )

    ExamScore.objects.filter(
        project=project,
        student_id__in=affected_student_ids
    ).delete()

    ExamScore.objects.bulk_create(
        insert_list,
        batch_size=5000
    )

    return JsonResponse({
        "status": "success",
        "registered": len(insert_list),
        "skipped_students": skipped_students,
        "skipped_combinations": skipped_combinations
    })


from django.db.models import Count


from django.db.models import Count, Prefetch

@login_required
def combination_list(request):

    combinations = (
        ExamCombination.objects
        .select_related("academic_level")
        .prefetch_related(
            Prefetch(
                "papers",
                queryset=CombinationPaper.objects
                .select_related("paper__subject")
                .order_by("paper__subject__subject_code")
            )
        )
        .annotate(
            papers_count=Count("papers", distinct=True)
        )
        .order_by("code")
    )

    levels = AcademicLevel.objects.all().order_by("name")

    return render(request, "exams/combination_list.html", {
        "combinations": combinations,
        "levels": levels,          # ✅ THIS WAS MISSING
    })




@login_required
@require_POST
def combination_add(request):
    code = request.POST.get("code", "").strip()
    name = request.POST.get("name", "").strip()
    category = request.POST.get("combination_category", "").strip()
    academic_level_id = request.POST.get("academic_level")

    # ------------------
    # VALIDATION
    # ------------------
    if not all([code, name, category, academic_level_id]):
        messages.error(request, "All fields are required.")
        return redirect("exams:combination_list")

    if ExamCombination.objects.filter(code=code).exists():
        messages.error(request, f"Combination {code} already exists.")
        return redirect("exams:combination_list")

    academic_level = AcademicLevel.objects.filter(id=academic_level_id).first()
    if not academic_level:
        messages.error(request, "Invalid academic level.")
        return redirect("exams:combination_list")

    # ------------------
    # SAVE
    # ------------------
    ExamCombination.objects.create(
        code=code,
        name=name,
        combination_category=category,
        academic_level=academic_level
    )

    messages.success(request, "Combination added successfully.")
    return redirect("exams:combination_list")




@login_required
@transaction.atomic
def combination_bulk_upload(request):

    if request.method != "POST":
        return redirect("exams:combination_list")

    file = request.FILES.get("excel_file")

    if not file:
        messages.error(request, "No file uploaded.")
        return redirect("exams:combination_list")

    df = pd.read_excel(file)

    required_columns = {"code","name", "combination_category", "academic_level_code"}

    if not required_columns.issubset(df.columns):
        messages.error(request, "Invalid Excel format.")
        return redirect("exams:combination_list")

    # Fast map academic level
    level_map = dict(
        AcademicLevel.objects.values_list("code", "id")
    )

    insert_list = []
    skipped = 0

    for _, row in df.iterrows():

        code = str(row["code"]).strip()
        name = str(row["name"]).strip()
        combination_category = str(row["combination_category"]).strip()
        level_code = str(row["academic_level_code"]).strip()

        level_id = level_map.get(level_code)

        if not level_id:
            skipped += 1
            continue

        insert_list.append(
            ExamCombination(
                code=code,
                name=name,
                combination_category=combination_category,
                academic_level_id=level_id
            )
        )

    ExamCombination.objects.bulk_create(
        insert_list,
        ignore_conflicts=True,
        batch_size=1000
    )

    messages.success(
        request,
        f"{len(insert_list)} combinations uploaded. {skipped} skipped."
    )

    return redirect("exams:combination_list")




@login_required
def combination_papers_manage(request, combination_id):

    combination = ExamCombination.objects.get(id=combination_id)

    all_papers = ExamSubjectPaper.objects.select_related(
        "subject"
    ).order_by("subject__subject_code", "paper_code")

    assigned = set(
        CombinationPaper.objects.filter(
            combination=combination
        ).values_list("paper_id", flat=True)
    )

    return render(request, "exams/combination_papers_manage.html", {
        "combination": combination,
        "papers": all_papers,
        "assigned": assigned
    })



@login_required
@transaction.atomic
def combination_papers_save(request, combination_id):

    combination = ExamCombination.objects.get(id=combination_id)

    selected_papers = request.POST.getlist("papers")

    # Clear existing
    CombinationPaper.objects.filter(
        combination=combination
    ).delete()

    insert_list = []

    for paper_id in selected_papers:
        insert_list.append(
            CombinationPaper(
                combination_id=combination_id,
                paper_id=paper_id
            )
        )

    CombinationPaper.objects.bulk_create(insert_list)

    messages.success(request, "Combination papers updated.")

    return redirect("exams:combination_list")



@login_required
def combination_papers_modal(request, combination_id):

    combination = ExamCombination.objects.get(id=combination_id)

    papers = ExamSubjectPaper.objects.select_related(
        "subject"
    ).order_by("subject__subject_code", "paper_code")

    assigned = set(
        CombinationPaper.objects.filter(
            combination=combination
        ).values_list("paper_id", flat=True)
    )

    return render(request, "exams/ajax_combination_papers_modal.html", {
        "combination": combination,
        "papers": papers,
        "assigned": assigned
    })


from django.http import JsonResponse
from django.db import transaction

@login_required
@transaction.atomic
def combination_papers_save_ajax(request, combination_id):

    if request.method != "POST":
        return JsonResponse({"status": "error"}, status=400)

    paper_ids = request.POST.getlist("papers[]")

    # Clear existing
    CombinationPaper.objects.filter(
        combination_id=combination_id
    ).delete()

    # Insert new
    insert_list = [
        CombinationPaper(
            combination_id=combination_id,
            paper_id=pid
        )
        for pid in paper_ids
    ]

    CombinationPaper.objects.bulk_create(insert_list)

    return JsonResponse({
        "status": "success",
        "count": len(insert_list)
    })





from django.db import transaction
from django.contrib import messages
from django.shortcuts import redirect, render, get_object_or_404
from django.contrib.auth.decorators import login_required
import openpyxl

@login_required
@transaction.atomic
def scripts_by_combination_upload(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    if request.method != "POST":
        return render(request, "exams/scripts_by_combination.html", {
            "project": project
        })

    file = request.FILES.get("excel_file")

    if not file:
        messages.error(request, "No file uploaded.")
        return redirect(
            "exams:scripts_by_combination_upload",
            project_id=project_id
        )

    # --------------------------------------------------
    # LOAD EXCEL
    # --------------------------------------------------
    try:
        wb = openpyxl.load_workbook(file, data_only=True)
        ws = wb.active
    except Exception:
        messages.error(request, "Invalid Excel file.")
        return redirect(
            "exams:scripts_by_combination_upload",
            project_id=project_id
        )

    # --------------------------------------------------
    # READ HEADER
    # --------------------------------------------------
    header = [str(c.value).strip().lower() for c in ws[1]]

    required_columns = {"student_number", "combination"}

    if not required_columns.issubset(set(header)):
        messages.error(request, "Invalid Excel format.")
        return redirect(
            "exams:scripts_by_combination_upload",
            project_id=project_id
        )

    col = {h: i for i, h in enumerate(header)}

    # --------------------------------------------------
    # FAST LOOKUP MAPS
    # --------------------------------------------------

    # 1️⃣ Students in this project
    students_map = dict(
        ProjectStudent.objects
        .filter(project=project)
        .values_list("student_number", "id")
    )

    # 2️⃣ Combination → Papers map
    combo_map = {
        c.code: list(
            c.papers.values_list("paper_id", flat=True)
        )
        for c in ExamCombination.objects.prefetch_related("papers")
    }

    insert_list = []
    skipped = 0

    # --------------------------------------------------
    # PROCESS ROWS
    # --------------------------------------------------
    for row in ws.iter_rows(min_row=2, values_only=True):

        student_number = str(row[col["student_number"]]).strip()
        combo_code = str(row[col["combination"]]).strip()

        student_id = students_map.get(student_number)
        paper_ids = combo_map.get(combo_code)

        if not student_id or not paper_ids:
            skipped += 1
            continue

        for pid in paper_ids:
            insert_list.append(
                ExamScore(
                    project_id=project_id,
                    student_id=student_id,
                    paper_id=pid
                )
            )

    # --------------------------------------------------
    # BULK INSERT
    # --------------------------------------------------
    ExamScore.objects.bulk_create(
        insert_list,
        ignore_conflicts=True,
        batch_size=5000
    )

    messages.success(
        request,
        f"{len(insert_list)} scripts registered. {skipped} skipped."
    )

    return redirect(
        "exams:scripts_by_combination_upload",
        project_id=project_id
    )




from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import A4
from django.http import HttpResponse
from django.utils.timezone import now
import math


@login_required
def project_school_subject_print(request, project_id, paper_id, school_id):

    project = JointExamProject.objects.get(id=project_id)
    paper = ExamSubjectPaper.objects.select_related("subject").get(id=paper_id)
    school = School.objects.get(id=school_id)

    scores = list(
        ExamScore.objects
        .filter(
            project_id=project_id,
            paper_id=paper_id,
            student__school_id=school_id
        )
        .select_related("student")
        .order_by("student__student_number")
    )

    # Layout settings
    maxPerCol = 30
    cols = 4
    maxPerPage = maxPerCol * cols
    total = len(scores)
    pages = math.ceil(total / maxPerPage)

    response = HttpResponse(content_type="application/pdf")
    response["Content-Disposition"] = (
        f'inline; filename="{school.school_code}_{paper.paper_code}.pdf"'
    )

    doc = SimpleDocTemplate(response, pagesize=A4)
    elements = []

    styles = getSampleStyleSheet()

    for page in range(pages):

        start = page * maxPerPage
        end = start + maxPerPage
        page_scores = scores[start:end]

        # Header
        elements.append(
            Paragraph("<b>SUBJECT SCORES</b>", styles["Heading2"])
        )
        elements.append(Spacer(1, 6))
        elements.append(
            Paragraph(
                f"<b>SCHOOL:</b> {school.school_code} - {school.name}",
                styles["Normal"]
            )
        )
        elements.append(
            Paragraph(
                f"<b>SUBJECT:</b> {paper.subject.subject_code} - {paper.subject.subject_name_eng}",
                styles["Normal"]
            )
        )
        elements.append(Spacer(1, 10))

        # Prepare empty grid
        data = []

        # Column header row
        header_row = []
        for c in range(cols):
            header_row.extend(["STUDENT NO", "SCORE"])
        data.append(header_row)

        # Fill rows downward (30 per column)
        for row_index in range(maxPerCol):

            row_data = []

            for col_index in range(cols):

                index = col_index * maxPerCol + row_index

                if index < len(page_scores):
                    s = page_scores[index]
                    cand = s.student.student_number
                    score = s.score if s.score is not None else ""
                else:
                    cand = ""
                    score = ""

                row_data.extend([cand, score])

            data.append(row_data)

        table = Table(data, colWidths=[1.2*inch, 0.6*inch]*cols)

        table.setStyle(TableStyle([
            ("GRID", (0,0), (-1,-1), 0.5, colors.black),
            ("BACKGROUND", (0,0), (-1,0), colors.lightgrey),
            ("ALIGN", (1,1), (-1,-1), "CENTER"),
            ("VALIGN", (0,0), (-1,-1), "MIDDLE"),
        ]))

        elements.append(table)
        elements.append(Spacer(1, 12))

        elements.append(
            Paragraph(
                f"ENTERED BY: ____________________        "
                f"{now().strftime('%Y-%m-%d %H:%M:%S')}",
                styles["Normal"]
            )
        )

        elements.append(
            Paragraph(
                f"Page {page+1} of {pages}",
                styles["Normal"]
            )
        )

        if page < pages - 1:
            elements.append(PageBreak())

    doc.build(elements)

    return response


@login_required
@transaction.atomic
def process_subject_scores(request, project_id, subject_id):

    papers = ExamSubjectPaper.objects.filter(subject_id=subject_id)

    students = ProjectStudent.objects.filter(
        examscore__project_id=project_id,
        examscore__paper__in=papers
    ).distinct()

    for student in students:

        scores = ExamScore.objects.filter(
            project_id=project_id,
            student=student,
            paper__in=papers,
            score_status=1
        )

        total = 0
        for s in scores:
            if s.score.upper() == "X":
                total = None
                break
            total += float(s.score)

        if total is None:
            continue

        max_total = sum(p.max_marks for p in papers)
        percentage = (total / max_total) * 100

        ProcessedSubjectScore.objects.update_or_create(
            project_id=project_id,
            student=student,
            subject_id=subject_id,
            defaults={
                "raw_total": total,
                "max_total": max_total,
                "percentage_score": percentage,
                "processed_by": request.user
            }
        )




















from decimal import Decimal, InvalidOperation
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, render
from openpyxl import load_workbook
from django.core.paginator import Paginator
from django.db.models import Q

@login_required
def upload_final_processed_scores(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    # =====================================================
    # POST → UPLOAD & PROCESS
    # =====================================================
    if request.method == "POST":

        file = request.FILES.get("excel_file")
        if not file:
            return JsonResponse({"ok": False, "error": "No file uploaded"}, status=400)

        try:
            wb = load_workbook(file, data_only=True)
            ws = wb.active

            HEADER_ROW = 1
            DATA_START_ROW = 3
            STUDENT_COL = 2
            FIRST_SUBJECT_COL = 6

            # ---------------- FAST LOOKUPS ----------------
            students_map = dict(
                ProjectStudent.objects
                .filter(project_id=project_id)
                .values_list("student_number", "id")
            )

            subjects_map = dict(
                ExamSubject.objects
                .values_list("subject_code", "subject_id")
            )

            grading_rules = list(
                GradingRule.objects
                .filter(academic_level=project.academic_level)
                .order_by("-min_score")
            )

            def resolve_grade_and_points(score):
                for r in grading_rules:
                    if r.min_score <= score <= r.max_score:
                        return r.grade, int(r.points * r.weight)
                return None, None

            # ---------------- SUBJECT HEADERS ----------------
            subject_columns = {}
            invalid_subject_cols = []

            for col in range(FIRST_SUBJECT_COL, ws.max_column + 1):
                raw = ws.cell(row=HEADER_ROW, column=col).value
                if not raw:
                    continue

                subject_code = str(raw).split("/")[0].strip()
                subject_id = subjects_map.get(subject_code)

                if subject_id:
                    subject_columns[col] = subject_id
                else:
                    invalid_subject_cols.append(
                        f"Column {col} skipped: invalid subject code ({subject_code})"
                    )

            if not subject_columns:
                return JsonResponse(
                    {"ok": False, "error": "No valid subject codes found"},
                    status=400
                )

            total_rows = max(ws.max_row - DATA_START_ROW + 1, 0)

            progress, _ = UploadProgress.objects.update_or_create(
                project=project,
                user=request.user,
                defaults={
                    "total_rows": total_rows,
                    "processed_rows": 0,
                    "status": "processing",
                }
            )

            bulk_insert = []
            skipped = 0
            processed = 0

            with transaction.atomic():

                for row in range(DATA_START_ROW, ws.max_row + 1):

                    student_number = ws.cell(row=row, column=STUDENT_COL).value
                    if not student_number:
                        skipped += 1
                        continue

                    student_id = students_map.get(str(student_number).strip())
                    if not student_id:
                        skipped += 1
                        continue

                    for col, subject_id in subject_columns.items():
                        cell = ws.cell(row=row, column=col).value

                        if cell in ("", None):
                            continue

                        # ---------- ABSENT ----------
                        if isinstance(cell, str) and cell.strip().upper() == "X":
                            continue  # do not insert

                        # ---------- NUMERIC ----------
                        try:
                            score = Decimal(str(cell))
                        except (InvalidOperation, TypeError):
                            skipped += 1
                            continue

                        if score < 0 or score > 100:
                            skipped += 1
                            continue

                        grade, points = resolve_grade_and_points(float(score))

                        bulk_insert.append(
                            ProcessedSubjectScore(
                                project_id=project_id,
                                student_id=student_id,
                                subject_id=subject_id,
                                percentage_score=float(score),
                                grade=grade,
                                points=points,
                                processed_by=request.user
                            )
                        )

                    processed += 1
                    UploadProgress.objects.filter(id=progress.id).update(
                        processed_rows=processed
                    )

                UploadProgress.objects.filter(id=progress.id).update(
                    status="saving"
                )

                ProcessedSubjectScore.objects.bulk_create(
                    bulk_insert,
                    ignore_conflicts=True,
                    batch_size=5000
                )

                report_lines = []
                report_lines.extend(invalid_subject_cols)
                report_lines.append(f"Inserted records: {len(bulk_insert)}")
                report_lines.append(f"Skipped rows: {skipped}")

                UploadProgress.objects.filter(id=progress.id).update(
                    status="done",
                    report="\n".join(report_lines)
                )

            return JsonResponse({"ok": True})

        except Exception as e:
            UploadProgress.objects.filter(
                project=project,
                user=request.user
            ).update(status="error")

            return JsonResponse(
                {"ok": False, "error": str(e)},
                status=500
            )

    # =====================================================
    # GET → PREVIEW TABLE (SEARCH + PAGINATION)
    # =====================================================
    search_query = request.GET.get("q", "").strip()

    scores_qs = (
        ProcessedSubjectScore.objects
        .filter(project_id=project_id)
        .select_related("student", "subject")
    )

    if search_query:
        scores_qs = scores_qs.filter(
            Q(student__student_number__icontains=search_query) |
            Q(subject__subject_code__icontains=search_query)
        )

    scores_qs = scores_qs.order_by("student__student_number")

    paginator = Paginator(scores_qs, 100)
    page_obj = paginator.get_page(request.GET.get("page"))

    table = {}
    subjects = []

    for s in page_obj:
        subjects.append(s.subject.subject_code)
        table.setdefault(s.student, {})[s.subject.subject_code] = s.percentage_score

    progress = UploadProgress.objects.filter(
        project=project,
        user=request.user
    ).order_by("-id").first()

    return render(request, "exams/final_processed_scores_upload.html", {
        "project": project,
        "table": table,
        "subjects": sorted(set(subjects)),
        "page_obj": page_obj,
        "search_query": search_query,
        "report": progress.report if progress else ""
    })





from openpyxl import Workbook
from django.http import HttpResponse

@login_required
def final_scores_template_download(request, project_id):

    wb = Workbook()
    ws = wb.active
    ws.title = "Final Scores"

    # Row 1 → Subject Codes
    headers = [
        "Centre",
        "student_number",
        "student_name",
        "sex",
        "combination",
        "65", "63", "141",
        "112/1", "112/2",
        "131/1", "131/2", "131/3"
    ]

    for col, value in enumerate(headers, start=1):
        ws.cell(row=1, column=col, value=value)

    # Row 2 → Descriptions (ignored by system)
    ws.append([
        "", "", "", "", "",
        "HISTORIA TANZANIA NA MAADILI",
        "ACADEMIC COMMUNICATION",
        "BASIC APPLIED MATHS",
        "HISTORY 1", "HISTORY 2",
        "PHYSICS 1", "PHYSICS 2", "PHYSICS PRACTICAL"
    ])

    # Example row
    ws.append([
        "S0702", "S0702-0501", "STUDENT NAME", "F", "HGK",
        66, 62, "", 65, 61, "", "", ""
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        'attachment; filename="final_processed_scores_template.xlsx"'
    )

    wb.save(response)
    return response


@login_required
def upload_progress_status(request, project_id):

    progress = UploadProgress.objects.filter(
        project_id=project_id,
        user=request.user
    ).order_by("-id").first()

    if not progress:
        return JsonResponse({"percent": 0})

    percent = int(
        (progress.processed_rows / progress.total_rows) * 100
    ) if progress.total_rows else 0

    return JsonResponse({
        "percent": percent,
        "processed": progress.processed_rows,
        "total": progress.total_rows,
        "status": progress.status
    })


@login_required
def update_scores_page(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    qs = (
        ExamScore.objects
        .filter(project_id=project_id)
        .select_related("student", "paper", "paper__subject")
        .only(
            "id",
            "score",
            "student__student_number",
            "paper__paper_code",
            "paper__subject__subject_name_eng",
        )
        .order_by("student__student_number")
    )

    q = request.GET.get("q", "").strip()
    if q:
        qs = qs.filter(
            Q(student__student_number__icontains=q) |
            Q(paper__paper_code__icontains=q)
        )

    paginator = Paginator(qs, 50)   # 🔥 50 rows per page
    page_obj = paginator.get_page(request.GET.get("page"))

    return render(request, "exams/update_scores.html", {
        "project": project,
        "page_obj": page_obj,
        "search_query": q
    })





from decimal import Decimal, InvalidOperation
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from openpyxl import load_workbook

@login_required
def bulk_update_scores(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    if request.method != "POST":
        return JsonResponse({"error": "Invalid request"}, status=400)

    file = request.FILES.get("excel_file")
    if not file:
        return JsonResponse({"error": "No file uploaded"}, status=400)

    wb = load_workbook(file, data_only=True)
    ws = wb.active

    # ----------------------------------
    # READ HEADER (ROW 2)
    # ----------------------------------
    header_row = 2
    headers = {
        str(cell.value).strip().lower(): idx
        for idx, cell in enumerate(ws[header_row], start=1)
        if cell.value
    }

    required = {"student_number", "paper_code", "score"}
    if not required.issubset(headers):
        return JsonResponse({
            "error": "Template must contain student_number, paper_code, score"
        }, status=400)

    STUDENT_COL = headers["student_number"]
    PAPER_COL = headers["paper_code"]
    SCORE_COL = headers["score"]

    DATA_START_ROW = header_row + 1
    TOTAL_ROWS = ws.max_row - DATA_START_ROW + 1

    # ----------------------------------
    # INIT PROGRESS
    # ----------------------------------
    progress, _ = UploadProgress.objects.update_or_create(
        project=project,
        user=request.user,
        defaults={
            "total_rows": TOTAL_ROWS,
            "processed_rows": 0,
            "status": "processing"
        }
    )

    # ----------------------------------
    # FAST LOOKUPS
    # ----------------------------------
    students_map = dict(
        ProjectStudent.objects
        .filter(project_id=project_id)
        .values_list("student_number", "id")
    )

    papers_map = dict(
        ExamSubjectPaper.objects
        .values_list("paper_code", "id")
    )

    scores_map = {
        (s.student_id, s.paper_id): s
        for s in ExamScore.objects
        .filter(project_id=project_id)
        .only("id", "student_id", "paper_id", "score")
    }

    to_update = []
    skipped = 0
    processed = 0

    # ----------------------------------
    # PROCESS ROWS
    # ----------------------------------
    for row in range(DATA_START_ROW, ws.max_row + 1):

        student_number = ws.cell(row=row, column=STUDENT_COL).value
        paper_code = ws.cell(row=row, column=PAPER_COL).value
        raw_score = ws.cell(row=row, column=SCORE_COL).value

        if not student_number or not paper_code:
            skipped += 1
            continue

        student_id = students_map.get(str(student_number).strip())
        paper_id = papers_map.get(str(paper_code).strip())

        if not student_id or not paper_id:
            skipped += 1
            continue

        score_obj = scores_map.get((student_id, paper_id))
        if not score_obj:
            skipped += 1
            continue

        from decimal import Decimal, InvalidOperation

        # ---------------- SCORE LOGIC ----------------
        raw = str(raw_score).strip() if raw_score is not None else ""

        # ABSENT
        if raw.upper() == "X":
            score_obj.score = "X"
            score_obj.score_status = 2   # ABSENT

        # NUMERIC (INTEGER OR DECIMAL)
        else:
            try:
                # validate numeric (but DO NOT convert)
                value = Decimal(raw)
            except (InvalidOperation, TypeError):
                skipped += 1
                continue

            if value < 0 or value > 100:
                skipped += 1
                continue

            # store EXACT text as entered (e.g. 56.5)
            score_obj.score = raw
            score_obj.score_status = 1   # UPDATED

        # AUDIT
        score_obj.updated_by_id = request.user.id
        to_update.append(score_obj)

        # ---------------- PROGRESS ----------------
        processed += 1
        UploadProgress.objects.filter(id=progress.id).update(
            processed_rows=processed
        )

    # ----------------------------------
    # SAVE
    # ----------------------------------
    UploadProgress.objects.filter(id=progress.id).update(status="saving")

    ExamScore.objects.bulk_update(
        to_update,
        ["score", "updated_by", "score_status", "updated_at"],
        batch_size=5000
    )

    UploadProgress.objects.filter(id=progress.id).update(status="done")

    return JsonResponse({
        "ok": True,
        "updated": len(to_update),
        "skipped": skipped
    })




from decimal import Decimal, InvalidOperation
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST


@login_required
@require_POST
def update_single_score(request, score_id):
    """
    Update a single student's score (AJAX).
    Accepts: 0–100 (integer/decimal) OR X/x for ABSENT
    """

    score_obj = get_object_or_404(ExamScore, id=score_id)

    raw = request.POST.get("score", "").strip()

    if not raw:
        return JsonResponse(
            {"ok": False, "error": "Score is required"},
            status=400
        )

    # ---------------- ABSENT ----------------
    if raw.upper() == "X":
        score_obj.score = "X"
        score_obj.score_status = 2   # ABSENT

    # ---------------- NUMERIC ----------------
    else:
        try:
            value = Decimal(raw)
        except InvalidOperation:
            return JsonResponse(
                {"ok": False, "error": "Invalid score value"},
                status=400
            )

        if value < 0 or value > 100:
            return JsonResponse(
                {"ok": False, "error": "Score must be between 0 and 100"},
                status=400
            )

        # store EXACT text (no rounding)
        score_obj.score = raw
        score_obj.score_status = 1   # UPDATED

    # ---------------- AUDIT ----------------
    score_obj.updated_by = request.user
    score_obj.save(
        update_fields=[
            "score",
            "updated_by",
            "score_status",
            "updated_at",
        ]
    )

    return JsonResponse({
        "ok": True,
        "score": score_obj.score,
        "status": score_obj.score_status
    })



from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from openpyxl import Workbook

@login_required
def update_scores_template(request, project_id):
    """
    Download Excel template for updating existing scores.
    """

    wb = Workbook()
    ws = wb.active
    ws.title = "Update Scores"

    # Header row
    ws.append([
        "student_number",
        "paper_code",
        "score"
    ])

    # Optional note row
    ws.append([
        "e.g. S0123-0456",
        "e.g. 112/1",
        "0 - 100 (decimals allowed)"
    ])

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = (
        f'attachment; filename="update_scores_template.xlsx"'
    )

    wb.save(response)
    return response



from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from openpyxl import Workbook
from openpyxl.styles import Font, Border, Side, Alignment


@login_required
def download_paper_registrations(request, project_id, paper_id):

    project = get_object_or_404(JointExamProject, id=project_id)
    paper = get_object_or_404(ExamSubjectPaper, id=paper_id)

    registrations = (
        ExamScore.objects
        .filter(project_id=project_id, paper_id=paper_id)
        .select_related("student", "student__school")
        .only(
            "student__student_number",
            "student__school__school_code",
            "student__school__name",
        )
        .order_by("student__student_number")
    )


    # ---------------- EXCEL SETUP ----------------
    wb = Workbook()
    ws = wb.active
    ws.title = safe_sheet_title(paper.paper_code)

    # Fonts & styles
    title_font = Font(name="Arial", size=14, bold=True)
    header_font = Font(name="Arial", bold=True)
    normal_font = Font(name="Arial")

    thin = Side(style="thin")
    border = Border(left=thin, right=thin, top=thin, bottom=thin)

    center = Alignment(horizontal="center", vertical="center")

    # ---------------- TITLE ROW ----------------
    ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=6)
    title_cell = ws.cell(row=1, column=1)
    title_cell.value = project.name
    title_cell.font = title_font
    title_cell.alignment = center

    # ---------------- HEADER ROW ----------------
    headers = [
        "school_code",
        "name",
        "paper_code",
        "paper_name",
        "student_number",
        "score",
    ]

    for col, header in enumerate(headers, start=1):
        cell = ws.cell(row=2, column=col)
        cell.value = header
        cell.font = header_font
        cell.border = border
        cell.alignment = center

    # ---------------- DATA ROWS ----------------
    row_num = 3
    for reg in registrations.iterator(chunk_size=5000):

        row_data = [
            reg.student.school.school_code,
            reg.student.school.name,          # ✅ REAL SCHOOL NAME
            paper.paper_code,
            paper.paper_name,
            reg.student.student_number,
            "",
        ]

        for col, value in enumerate(row_data, start=1):
            cell = ws.cell(row=row_num, column=col)
            cell.value = value
            cell.font = normal_font
            cell.border = border

        row_num += 1

    # ---------------- COLUMN WIDTHS ----------------
    widths = [15, 20, 15, 30, 20, 10]
    for i, w in enumerate(widths, start=1):
        ws.column_dimensions[chr(64 + i)].width = w

    # ---------------- FILE NAME ----------------
    safe_code = paper.paper_code.replace("/", "_")
    filename = f"{safe_code}-{paper.paper_name}-Template.xlsx"

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response




from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from django.views.decorators.http import require_POST
from django.db import transaction

@login_required
@require_POST
@transaction.atomic
def delete_all_processed_scores(request, project_id):
    """
    Completely delete all processed scores for a project.
    """

    project = get_object_or_404(JointExamProject, id=project_id)

    qs = ProcessedSubjectScore.objects.filter(project_id=project_id)
    deleted_count = qs.count()

    qs.delete()

    return JsonResponse({
        "ok": True,
        "deleted": deleted_count,
        "message": f"{deleted_count} records deleted successfully."
    })



@login_required
def rules_management(request):

    academic_levels = AcademicLevel.objects.all()

    level_id = request.GET.get("level")
    selected_level = None

    grading_rules = []
    division_rules = []
    gpa_rules = []
    subject_rules = []


    if level_id:
        selected_level = AcademicLevel.objects.get(id=level_id)

        grading_rules = GradingRule.objects.filter(
            academic_level=selected_level
        )

        division_rules = DivisionRule.objects.filter(
            academic_level=selected_level
        )

        gpa_rules = GpaRule.objects.filter(
            academic_level=selected_level
        )

        subject_rules = SubjectProcessingRule.objects.filter(
            academic_level=selected_level
        ).select_related("subject")

    return render(request, "exams/rules_management.html", {
        "academic_levels": academic_levels,
        "selected_level": selected_level,
        "grading_rules": grading_rules,
        "division_rules": division_rules,
        "gpa_rules": gpa_rules,
        "subject_rules": subject_rules,
    })


@require_POST
@login_required
def save_grading_rule(request):
    rule_id = request.POST.get("id")

    obj = GradingRule.objects.get(id=rule_id) if rule_id else GradingRule()

    obj.academic_level_id = request.POST["academic_level"]
    obj.min_score = request.POST["min_score"]
    obj.max_score = request.POST["max_score"]
    obj.grade = request.POST["grade"]
    obj.points = request.POST["points"]
    obj.status = request.POST["status"]
    obj.weight = request.POST.get("weight", 1)

    obj.save()
    return JsonResponse({"ok": True})



@require_POST
@login_required
def save_division_rule(request):
    rule_id = request.POST.get("id")

    obj = DivisionRule.objects.get(id=rule_id) if rule_id else DivisionRule()

    obj.academic_level_id = request.POST["academic_level"]
    obj.min_points = request.POST["min_points"]
    obj.max_points = request.POST["max_points"]
    obj.division = request.POST["division"]

    obj.save()
    return JsonResponse({"ok": True})

@require_POST
@login_required
def save_gpa_rule(request):
    rule_id = request.POST.get("id")

    # EDIT or CREATE
    obj = GpaRule.objects.get(id=rule_id) if rule_id else GpaRule()

    obj.academic_level_id = request.POST["academic_level"]
    obj.min_gpa = request.POST["min_gpa"]
    obj.max_gpa = request.POST["max_gpa"]
    obj.gpa_grade = request.POST["gpa_grade"]
    obj.gpa_points = request.POST["gpa_points"]
    obj.gpa_status = request.POST["gpa_status"]
    obj.gpa_colour = request.POST["gpa_colour"]

    obj.save()
    return JsonResponse({"ok": True})

@require_POST
@login_required
def save_subject_rule(request):
    rule_id = request.POST.get("id")

    obj = SubjectProcessingRule.objects.get(id=rule_id) if rule_id else SubjectProcessingRule()

    obj.academic_level_id = request.POST["academic_level"]
    obj.subject_id = request.POST["subject"]
    obj.total_max_marks = request.POST["total_max_marks"]
    obj.formula = request.POST["formula"]

    obj.save()
    return JsonResponse({"ok": True})



import csv
from django.http import StreamingHttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404

@login_required
def export_all_subject_scores_csv(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    # 🔥 Query optimized for large data
    qs = (
        ProcessedSubjectScore.objects
        .filter(project_id=project_id)
        .select_related(
            "student",
            "student__school",
            "subject",
        )
        .order_by("student__student_number")
        .iterator(chunk_size=10000)
    )

    # ---------------- CSV STREAMER ----------------
    class Echo:
        def write(self, value):
            return value

    pseudo_buffer = Echo()
    writer = csv.writer(pseudo_buffer)

    def stream():
        # Project title (first row)
        yield writer.writerow([project.name])
        yield writer.writerow([])

        # Header
        yield writer.writerow([
            "school_code",
            "school_name",
            "subject_code",
            "subject_name",
            "student_number",
            "score",
        ])

        # Data rows
        for s in qs:
            yield writer.writerow([
                s.student.school.school_code if s.student.school else "",
                s.student.school.name if s.student.school else "",
                s.subject.subject_code,
                s.subject.subject_name_eng,
                s.student.student_number,
                s.percentage_score,
            ])

    filename = f"ALL-SUBJECT-SCORES-{project.name}.csv"

    response = StreamingHttpResponse(
        stream(),
        content_type="text/csv"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    return response


import csv
from django.http import StreamingHttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404

@login_required
def export_exam_scores_csv(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    # 🔥 Optimized queryset (streaming-safe)
    qs = (
        ExamScore.objects
        .filter(project_id=project_id)
        .select_related(
            "student",
            "student__school",
            "paper",
            "paper__subject",
        )
        .order_by(
            "student__student_number",
            "paper__paper_code"
        )
        .iterator(chunk_size=10000)
    )

    # ---------------- CSV STREAMER ----------------
    class Echo:
        def write(self, value):
            return value

    pseudo_buffer = Echo()
    writer = csv.writer(pseudo_buffer)

    def stream():
        # 🔹 Project title
        yield writer.writerow([project.name])
        yield writer.writerow([])

        # 🔹 Header
        yield writer.writerow([
            "school_code",
            "school_name",
            "paper_code",
            "paper_name",
            "student_number",
            "score",
            "score_status",
            "updated_at",
        ])

        # 🔹 Data rows
        for s in qs:
            yield writer.writerow([
                s.student.school.school_code if s.student.school else "",
                s.student.school.name if s.student.school else "",
                s.paper.paper_code,
                s.paper.paper_name,
                s.student.student_number,
                s.score,
                s.score_status,
                s.updated_at.strftime("%Y-%m-%d %H:%M:%S") if s.updated_at else "",
            ])

    filename = f"EXAM-SCORES-{project.name}.csv"

    response = StreamingHttpResponse(
        stream(),
        content_type="text/csv"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    return response



import csv
from django.http import StreamingHttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404

@login_required
def export_processed_scores_pivot_csv(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    # -------------------------------------------------
    # 1️⃣ GET ALL SUBJECTS (COLUMN ORDER IS IMPORTANT)
    # -------------------------------------------------
    subjects = list(
        ExamSubject.objects
        .filter(processedsubjectscore__project_id=project_id)
        .distinct()
        .order_by("subject_code")
        .values("subject_id", "subject_code", "subject_name_eng")
    )

    subject_ids = [s["subject_id"] for s in subjects]

    # -------------------------------------------------
    # 2️⃣ STREAM ALL SCORES (ORDERED BY STUDENT)
    # -------------------------------------------------
    scores_qs = (
        ProcessedSubjectScore.objects
        .filter(project_id=project_id)
        .select_related("student", "student__school", "subject", "student__combination")
        .order_by("student__student_number")
        .iterator(chunk_size=10000)
    )

    # -------------------------------------------------
    # 3️⃣ CSV STREAMER
    # -------------------------------------------------
    class Echo:
        def write(self, value):
            return value

    pseudo_buffer = Echo()
    writer = csv.writer(pseudo_buffer)

    def stream():

        # 🔹 ROW 1: STUDENT DETAILS + SUBJECT CODES
        yield writer.writerow(
            ["STUDENT DETAILS", "", "", "", ""] +
            [s["subject_code"] for s in subjects]
        )

        # 🔹 ROW 2: FULL HEADERS
        yield writer.writerow(
            ["C.REG", "C/NO", "STUDENTS' NAME", "SEX", "COMB"] +
            [s["subject_name_eng"] for s in subjects]
        )

        current_student = None
        row = None
        subject_index = {sid: i for i, sid in enumerate(subject_ids)}

        # -------------------------------------------------
        # 4️⃣ BUILD ROW PER STUDENT (STREAMING)
        # -------------------------------------------------
        for s in scores_qs:

            student = s.student

            # 🔄 New student → flush previous row
            if current_student != student.id:
                if row:
                    yield writer.writerow(row)

                row = [
                    student.school.school_code if student.school else "",
                    student.student_number,
                    student.full_name,
                    student.sex,
                    student.combination.code if student.combination else "",
                ] + [""] * len(subjects)

                current_student = student.id

            # 🔹 Place score in correct subject column
            idx = subject_index.get(s.subject_id)
            if idx is not None:
                row[5 + idx] = s.percentage_score

        # 🔚 LAST STUDENT
        if row:
            yield writer.writerow(row)

    filename = f"FINAL-PROCESSED-SCORES-{project.name}.csv"

    response = StreamingHttpResponse(stream(), content_type="text/csv")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    return response



import csv
from django.http import StreamingHttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404

@login_required
def export_exam_scores_pivot_csv(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    # -------------------------------------------------
    # 1️⃣ GET ALL PAPERS USED IN THIS PROJECT (COLUMNS)
    # -------------------------------------------------
    papers = list(
        ExamSubjectPaper.objects
        .filter(examscore__project_id=project_id)
        .distinct()
        .order_by("paper_code")
        .values(
            "id",
            "paper_code",
            "paper_name",
            "subject__subject_name_eng",
        )
    )

    paper_ids = [p["id"] for p in papers]

    # -------------------------------------------------
    # 2️⃣ STREAM ALL EXAM SCORES (ORDERED BY STUDENT)
    # -------------------------------------------------
    scores_qs = (
        ExamScore.objects
        .filter(project_id=project_id)
        .select_related(
            "student",
            "student__school",
            "student__combination",
            "paper",
        )
        .order_by(
            "student__student_number",
            "paper__paper_code",
        )
        .iterator(chunk_size=10000)
    )

    # -------------------------------------------------
    # 3️⃣ CSV STREAMER
    # -------------------------------------------------
    class Echo:
        def write(self, value):
            return value

    pseudo_buffer = Echo()
    writer = csv.writer(pseudo_buffer)

    def stream():

        # 🔹 ROW 1: STUDENT DETAILS + PAPER CODES
        yield writer.writerow(
            ["STUDENT DETAILS", "", "", "", ""] +
            [p["paper_code"] for p in papers]
        )

        # 🔹 ROW 2: FULL HEADERS
        yield writer.writerow(
            ["C.REG", "C/NO", "STUDENTS' NAME", "SEX", "COMB"] +
            [
                f'{p["subject__subject_name_eng"]} ({p["paper_code"]})'
                for p in papers
            ]
        )

        current_student_id = None
        row = None

        paper_index = {
            pid: idx for idx, pid in enumerate(paper_ids)
        }

        # -------------------------------------------------
        # 4️⃣ BUILD ONE ROW PER STUDENT
        # -------------------------------------------------
        for s in scores_qs:

            student = s.student

            # 🔄 New student → flush previous row
            if current_student_id != student.id:
                if row:
                    yield writer.writerow(row)

                row = [
                    student.school.school_code if student.school else "",
                    student.student_number,
                    student.full_name,
                    student.sex,
                    student.combination.code if student.combination else "",
                ] + [""] * len(papers)

                current_student_id = student.id

            # 🔹 Place score in correct paper column
            idx = paper_index.get(s.paper_id)
            if idx is not None:
                row[5 + idx] = s.score

        # 🔚 LAST STUDENT
        if row:
            yield writer.writerow(row)

    filename = f"EXAM-SCORES-PIVOT-{project.name}.csv"

    response = StreamingHttpResponse(stream(), content_type="text/csv")
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    return response



@login_required
def generate_student_final_results(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)
    level_id = project.academic_level_id

    # ---------------- LOAD RULES ONCE ----------------
    grading_rules = list(
        GradingRule.objects
        .filter(academic_level=project.academic_level)
        .order_by("-min_score")
    )

    division_rules = list(
        DivisionRule.objects
        .filter(academic_level=project.academic_level)
        .order_by("min_points")
    )

    # ---------------- STUDENTS ----------------
    students = list(
        ProjectStudent.objects
        .filter(project_id=project_id)
        .select_related("school", "combination")
    )

    total_students = len(students)

    progress, _ = UploadProgress.objects.update_or_create(
        project=project,
        user=request.user,
        defaults={
            "total_rows": total_students,
            "processed_rows": 0,
            "status": "processing"
        }
    )

    # ---------------- SCORES GROUPED ----------------
    scores_qs = (
        ProcessedSubjectScore.objects
        .filter(project_id=project_id)
        .select_related("subject")
    )

    scores_map = {}
    for s in scores_qs:
        scores_map.setdefault(s.student_id, []).append(s)

    results_bulk = []
    processed = 0

    with transaction.atomic():

        for student in students:

            subjects = scores_map.get(student.id, [])

            # ---------------- NO SUBJECTS → ABS ----------------
            if not subjects or all(s.percentage_score in (None, 0) for s in subjects):
                results_bulk.append(
                    StudentFinalResult(
                        project=project,
                        student=student,
                        std_average="X",
                        std_averagegrade="X",
                        total_points=0,
                        division="ABS",
                        full_results="X"
                    )
                )
                processed += 1
                UploadProgress.objects.filter(id=progress.id).update(
                    processed_rows=processed
                )
                continue


            from decimal import Decimal, InvalidOperation

            # ---------------- FULL RESULTS ----------------
            full_parts = []
            total_score = Decimal("0")
            counted = 0
            points_list = []

            for s in sorted(subjects, key=lambda x: x.subject.subject_code):

                raw_score = s.percentage_score

                # ---------------- ABSENT ----------------
                if raw_score in (None, "", "X"):
                    full_parts.append(f'{s.subject.subject_code}-"X"')
                    continue

                # ---------------- SAFE NUMERIC ----------------
                try:
                    score = Decimal(str(raw_score))
                except (InvalidOperation, TypeError):
                    full_parts.append(f'{s.subject.subject_code}-"X"')
                    continue

                # ---------------- RANGE SAFETY ----------------
                if score < 0 or score > 100:
                    full_parts.append(f'{s.subject.subject_code}-"X"')
                    continue

                # ---------------- VALID SUBJECT ----------------
                full_parts.append(f"{s.subject.subject_shortname}-'{s.grade or 'X'}'")

                total_score += score
                counted += 1

                if s.subject.is_principal and s.points is not None:
                    try:
                        points_list.append(int(s.points))
                    except (TypeError, ValueError):
                        pass

            # ---------------- AVERAGE ----------------
            if counted == 0:
                std_average = "X"
                std_averagegrade = "X"
            else:
                avg = (total_score / counted).quantize(Decimal("0.01"))
                std_average = str(avg)
                std_averagegrade = resolve_average_grade(avg, grading_rules)

            # ---------------- FINAL STRING ----------------
            full_results = ', '.join(full_parts)


            # ---------------- POINTS & DIVISION ----------------
            division = "-"
            total_points = None   # None = not applicable / ABS

            if level_id in (1, 2):

                best_n = 7 if level_id == 1 else 3

                # ✅ ONLY numeric points from PRINCIPAL subjects
                numeric_points = [
                    int(p) for p in points_list
                    if isinstance(p, (int, float))
                ]

                if not numeric_points:
                    # ❌ no valid principal subject
                    division = "ABS"
                    total_points = None

                else:
                    best_points = sorted(numeric_points)[:best_n]
                    total_points = sum(best_points)

                    # ✅ resolve division
                    for r in division_rules:
                        if r.min_points <= total_points <= r.max_points:
                            division = r.division
                            break

            elif level_id == 3:
                # 🔕 NO points / division calculation
                division = "-"
                total_points = None

            # ---------------- SAVE RESULT ----------------
            results_bulk.append(
                StudentFinalResult(
                    project=project,
                    student=student,
                    std_average=str(avg) if avg else "X",
                    std_averagegrade=std_averagegrade,
                    total_points=total_points,
                    division=division,
                    full_results=", ".join(full_parts)
                )
            )

            processed += 1
            UploadProgress.objects.filter(id=progress.id).update(
                processed_rows=processed
            )

        StudentFinalResult.objects.filter(project=project).delete()
        StudentFinalResult.objects.bulk_create(results_bulk, batch_size=2000)

        UploadProgress.objects.filter(id=progress.id).update(status="done")

    return JsonResponse({"ok": True})



@login_required
def student_final_results_report(request, project_id):

    q = request.GET.get("q", "").strip()

    qs = (
        StudentFinalResult.objects
        .filter(project_id=project_id)
        .select_related("student", "student__school", "student__combination")
    )

    if q:
        qs = qs.filter(
            Q(student__student_number__icontains=q) |
            Q(student__full_name__icontains=q) |
            Q(division__icontains=q)
        )

    paginator = Paginator(qs.order_by("student__student_number"), 50)
    page_obj = paginator.get_page(request.GET.get("page"))

    return render(request, "exams/student_final_results_report.html", {
        "page_obj": page_obj,
        "search_query": q,
        "project_id": project_id
    })



@login_required
def process_student_results_page(request, project_id):
    project = get_object_or_404(JointExamProject, id=project_id)

    progress = UploadProgress.objects.filter(
        project=project,
        user=request.user
    ).order_by("-id").first()

    return render(request, "exams/process_student_results.html", {
        "project": project,
        "progress": progress
    })



from openpyxl import Workbook
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404

@login_required
def download_papers_upload_template(request, project_id):
    """
    Download Excel template:
    student_number | paper_code1 | ... | paper_code15
    """

    project = get_object_or_404(JointExamProject, id=project_id)

    # Fetch students only (fast)
    students = (
        ProjectStudent.objects
        .filter(project_id=project_id)
        .only("student_number")
        .order_by("student_number")
        .iterator(chunk_size=5000)
    )

    wb = Workbook()
    ws = wb.active
    ws.title = "Paper Upload Template"

    # ---------------- HEADER ----------------
    headers = ["student_number"]
    headers += [f"paper_code{i}" for i in range(1, 16)]

    ws.append(headers)

    # ---------------- DATA ----------------
    for s in students:
        row = [s.student_number] + [""] * 15
        ws.append(row)

    # ---------------- RESPONSE ----------------
    filename = f"{project.name}-Paper-Upload-Template.xlsx"

    response = HttpResponse(
        content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    )
    response["Content-Disposition"] = f'attachment; filename="{filename}"'

    wb.save(response)
    return response



@login_required
@require_POST
def generate_school_results(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)
    level_id = project.academic_level_id

    grading_rules = list(
        GradingRule.objects.filter(
            academic_level_id=level_id
        ).order_by("-min_score")
    )

    gpa_rules = list(
        GpaRule.objects.filter(
            academic_level_id=level_id
        ).order_by("-min_gpa")
    )

    # ================= STUDENT RESULTS =================
    students = (
        StudentFinalResult.objects
        .filter(project_id=project_id)
        .select_related("student__school")
    )

    schools = {}

    for s in students:
        school = s.student.school
        if not school:
            continue

        data = schools.setdefault(school.id, {
            "school": school,
            "avg_sum": 0,
            "avg_count": 0,
            "grades": [],
            "divisions": [],
        })

        if s.std_average not in ("X", None):
            data["avg_sum"] += float(s.std_average)
            data["avg_count"] += 1

        if s.std_averagegrade:
            data["grades"].append(s.std_averagegrade)

        if s.division not in ("-", "ABS", None):
            data["divisions"].append(s.division)

    # ================= SUBJECT GRADES =================
    subject_qs = (
        ProcessedSubjectScore.objects
        .filter(project_id=project_id)
        .select_related("student__school")
        .values("student__school_id", "grade")
    )

    subject_map = {}
    for r in subject_qs:
        sid = r["student__school_id"]
        subject_map.setdefault(sid, []).append(r["grade"])

    results = []

    for school_id, data in schools.items():
        school = data["school"]

        # ---------- SCHOOL AVERAGE ----------
        sch_avg = (
            round(data["avg_sum"] / data["avg_count"], 2)
            if data["avg_count"] else None
        )

        sch_avg_grd = sch_avg_status = None
        if sch_avg is not None:
            for g in grading_rules:
                if g.min_score <= sch_avg <= g.max_score:
                    sch_avg_grd = g.grade
                    sch_avg_status = g.grade
                    break

        # ---------- SUBJECT GPA ----------
        subj_weights = SUBJECT_GPA_RULES.get(level_id, {})
        subj_counts = {}

        for g in subject_map.get(school_id, []):
            if g in subj_weights:
                subj_counts[g] = subj_counts.get(g, 0) + 1

        subj_total = sum(subj_counts.values())
        subj_gpa = (
            sum(subj_counts[g] * subj_weights[g] for g in subj_counts) / subj_total
            if subj_total else None
        )

        # ---------- DIVISION GPA ----------
        div_counts = {}
        for d in data["divisions"]:
            div_counts[d] = div_counts.get(d, 0) + 1

        div_total = sum(div_counts.values())
        div_gpa = (
            sum(div_counts[d] * DIVISION_GPA_RULES[d] for d in div_counts) / div_total
            if div_total else None
        )

        # ---------- FINAL GPA ----------
        if level_id == 3:
            sch_gpa = subj_gpa
        else:
            sch_gpa = (
                round((subj_gpa + div_gpa) / 2, 2)
                if subj_gpa is not None and div_gpa is not None
                else None
            )

        sch_gpa_grade = sch_gpa_status = None
        if sch_gpa is not None:
            for r in gpa_rules:
                if r.min_gpa <= sch_gpa <= r.max_gpa:
                    sch_gpa_grade = r.gpa_grade
                    sch_gpa_status = r.gpa_status
                    break

        results.append(
            ExamSchoolResult(
                project=project,
                school=school,
                with_results=data["avg_count"],
                sch_avg=sch_avg,
                sch_avg_grd=sch_avg_grd,
                sch_avg_status=sch_avg_status,
                sch_gpa=sch_gpa,
                sch_gpa_grade=sch_gpa_grade,
                sch_gpa_status=sch_gpa_status,
            )
        )

    # 🔥 FAST SAVE
    ExamSchoolResult.objects.filter(project=project).delete()
    ExamSchoolResult.objects.bulk_create(results, batch_size=500)

    return JsonResponse({
        "ok": True,
        "schools_processed": len(results)
    })




@login_required
def school_ranking_view(request, project_id):

    project = get_object_or_404(JointExamProject, id=project_id)

    search_query = request.GET.get("q", "").strip()

    qs = (
        ExamSchoolResult.objects
        .filter(
            project=project,
            sch_rank_involvement=True,
        )
        .select_related("school")
    )

    # 🔍 SEARCH
    if search_query:
        qs = qs.filter(
            Q(school__school_code__icontains=search_query) |
            Q(school__name__icontains=search_query)
        )

    # 🥇 RANK ORDER (LOWER GPA IS BETTER)
    qs = qs.order_by("sch_gpa", "school__school_code")

    paginator = Paginator(qs, 50)
    page_number = request.GET.get("page")
    page_obj = paginator.get_page(page_number)

    # ================= RANK CALCULATION =================
    ranked_rows = []
    current_rank = 0
    last_gpa = None
    display_rank = 0

    for row in page_obj:
        display_rank += 1

        if row.sch_gpa != last_gpa:
            current_rank = display_rank
            last_gpa = row.sch_gpa

        ranked_rows.append({
            "rank": current_rank,
            "obj": row
        })

    return render(
        request,
        "exams/school_ranking.html",
        {
            "project": project,
            "ranked_rows": ranked_rows,
            "page_obj": page_obj,
            "search_query": search_query,
        }
    )
