from functools import wraps
from decimal import Decimal, InvalidOperation

from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.db.models import Count, Q
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone

from app.models.classes import AcademicClass, Class, Term
from app.models.school_settings import AcademicYear, SchoolSetting, Section
from app.models.students import ClassRegister, Student
from app.models.subjects import Subject
from app.services.school_level import get_active_school_level
from secondary.forms import (
    ALevelCompetencyScaleForm,
    ALevelComponentWeightForm,
    ALevelModuleAssessmentForm,
    ALevelResultPreviewForm,
    ALevelSubjectModuleForm,
    ContinuousAssessmentTaskForm,
    SecondaryCompetencyForm,
    SecondaryComputationPolicyForm,
    SecondaryGradeBandForm,
    SecondaryResultPreviewForm,
    SubjectCompetencyForm,
    UNEBSubmissionBatchForm,
)
from secondary.models import (
    ALevelCompetencyScale,
    ALevelComponentWeight,
    ALevelModuleAssessment,
    ALevelSubjectModule,
    CAModeration,
    ContinuousAssessmentRecord,
    ContinuousAssessmentTask,
    SecondaryComputationPolicy,
    SubjectCompetency,
    UNEBSubmissionBatch,
)
from secondary.services.alevel import compute_a_level_subject_result
from secondary.services.cbc import build_uneb_batch_items, compute_subject_result, lock_uneb_batch


def secondary_mode_required(view_func):
    @wraps(view_func)
    def _wrapped(request, *args, **kwargs):
        active_level = get_active_school_level(request)
        if active_level == SchoolSetting.EducationLevel.PRIMARY:
            messages.error(
                request,
                "Secondary CBC is disabled. Set School Settings to a Secondary mode first.",
            )
            return redirect(f"{reverse('settings_page')}#general")
        return view_func(request, *args, **kwargs)

    return _wrapped


def secondary_lower_mode_required(view_func):
    @wraps(view_func)
    def _wrapped(request, *args, **kwargs):
        active_level = get_active_school_level(request)
        if active_level != SchoolSetting.EducationLevel.SECONDARY_LOWER:
            messages.error(
                request,
                "This workflow is for O-Level (S1-S4). Switch active school level to Secondary (O-Level).",
            )
            return redirect("secondary:dashboard")
        return view_func(request, *args, **kwargs)

    return _wrapped


def secondary_upper_mode_required(view_func):
    @wraps(view_func)
    def _wrapped(request, *args, **kwargs):
        active_level = get_active_school_level(request)
        if active_level != SchoolSetting.EducationLevel.SECONDARY_UPPER:
            messages.error(
                request,
                "This workflow is for A-Level (S5-S6). Switch active school level to Secondary (A-Level).",
            )
            return redirect("secondary:dashboard")
        return view_func(request, *args, **kwargs)

    return _wrapped


def _current_policy_level(request):
    active_level = get_active_school_level(request)
    if active_level == SchoolSetting.EducationLevel.SECONDARY_UPPER:
        return SecondaryComputationPolicy.Level.UPPER_SECONDARY
    return SecondaryComputationPolicy.Level.LOWER_SECONDARY


def _secondary_sections_queryset(active_level=None):
    sections = Section.objects.filter(Section.secondary_filter())
    generic_sections = sections.filter(Section.generic_secondary_filter())

    if active_level == SchoolSetting.EducationLevel.SECONDARY_LOWER:
        lower_sections = sections.filter(Section.lower_secondary_filter())
        if lower_sections.exists():
            return lower_sections
        if generic_sections.exists():
            return generic_sections
        return Section.objects.none() if sections.exists() else Section.objects.all()
    elif active_level == SchoolSetting.EducationLevel.SECONDARY_UPPER:
        upper_sections = sections.filter(Section.upper_secondary_filter())
        if upper_sections.exists():
            return upper_sections
        if generic_sections.exists():
            return generic_sections
        return Section.objects.none() if sections.exists() else Section.objects.all()

    return sections if sections.exists() else Section.objects.all()


def _class_level_filter(active_level, field_prefix=""):
    if active_level == SchoolSetting.EducationLevel.SECONDARY_UPPER:
        code_values = ["S5", "S6"]
        name_values = ["Senior 5", "Senior 6"]
    else:
        code_values = ["S1", "S2", "S3", "S4"]
        name_values = ["Senior 1", "Senior 2", "Senior 3", "Senior 4"]

    level_filter = Q(**{f"{field_prefix}code__in": code_values})
    for name in name_values:
        level_filter |= Q(**{f"{field_prefix}name__icontains": name})
    return level_filter


def _secondary_subjects_queryset(active_level=None):
    return Subject.objects.filter(section__in=_secondary_sections_queryset(active_level=active_level)).order_by("name")


def _secondary_classes_queryset(active_level=None):
    queryset = Class.objects.filter(section__in=_secondary_sections_queryset(active_level=active_level)).order_by("name")
    if not active_level:
        return queryset
    filtered = queryset.filter(_class_level_filter(active_level))
    return filtered if filtered.exists() else queryset


def _secondary_academic_classes_queryset(active_level=None):
    queryset = AcademicClass.objects.filter(section__in=_secondary_sections_queryset(active_level=active_level)).select_related(
        "Class", "term", "academic_year"
    ).order_by("-academic_year__academic_year", "-term__start_date", "Class__name")
    if not active_level:
        return queryset
    filtered = queryset.filter(_class_level_filter(active_level, field_prefix="Class__"))
    return filtered if filtered.exists() else queryset


def _secondary_students_queryset(active_level=None):
    class_ids = _secondary_classes_queryset(active_level=active_level).values_list("id", flat=True)
    queryset = Student.objects.filter(current_class_id__in=class_ids, is_active=True).order_by("student_name")
    if queryset.exists():
        return queryset
    return Student.objects.filter(
        current_class__section__in=_secondary_sections_queryset(active_level=active_level), is_active=True
    ).order_by(
        "student_name"
    )


def _bind_secondary_form_querysets(form, active_level=None):
    sections = _secondary_sections_queryset(active_level=active_level)
    subjects = _secondary_subjects_queryset(active_level=active_level)
    classes = _secondary_classes_queryset(active_level=active_level)
    academic_classes = _secondary_academic_classes_queryset(active_level=active_level)

    if "section" in form.fields:
        form.fields["section"].queryset = sections
    if "subject" in form.fields:
        form.fields["subject"].queryset = subjects
    if "academic_class" in form.fields:
        form.fields["academic_class"].queryset = academic_classes
    if "term" in form.fields:
        form.fields["term"].queryset = Term.objects.filter(academic_year__in=AcademicYear.objects.all()).order_by(
            "-academic_year__academic_year", "start_date"
        )
    if "subject_competency" in form.fields:
        form.fields["subject_competency"].queryset = SubjectCompetency.objects.filter(
            section__in=sections
        ).select_related("subject", "competency")
    if "candidate_class" in form.fields:
        form.fields["candidate_class"].queryset = classes


def _refresh_task_status(task):
    statuses = set(task.records.values_list("moderation_status", flat=True))
    if not statuses:
        new_status = ContinuousAssessmentTask.Status.DRAFT
    elif statuses.issubset(
        {
            ContinuousAssessmentRecord.ModerationStatus.APPROVED,
            ContinuousAssessmentRecord.ModerationStatus.LOCKED,
        }
    ):
        new_status = ContinuousAssessmentTask.Status.APPROVED
    elif statuses.issubset(
        {
            ContinuousAssessmentRecord.ModerationStatus.MODERATED,
            ContinuousAssessmentRecord.ModerationStatus.APPROVED,
            ContinuousAssessmentRecord.ModerationStatus.LOCKED,
        }
    ):
        new_status = ContinuousAssessmentTask.Status.MODERATED
    elif ContinuousAssessmentRecord.ModerationStatus.REJECTED in statuses:
        new_status = ContinuousAssessmentTask.Status.PENDING_MODERATION
    elif ContinuousAssessmentRecord.ModerationStatus.PENDING in statuses:
        new_status = ContinuousAssessmentTask.Status.PENDING_MODERATION
    else:
        new_status = ContinuousAssessmentTask.Status.DRAFT

    if task.status != new_status:
        task.status = new_status
        task.save(update_fields=["status", "updated_at"])


@login_required
@secondary_mode_required
def secondary_dashboard(request):
    active_level = get_active_school_level(request)
    policy_level = _current_policy_level(request)
    policy_qs = SecondaryComputationPolicy.objects.filter(level=policy_level)

    context = {
        "policy_count": policy_qs.count(),
        "is_upper_mode": active_level == SchoolSetting.EducationLevel.SECONDARY_UPPER,
    }
    if active_level == SchoolSetting.EducationLevel.SECONDARY_UPPER:
        context.update(
            {
                "a_level_scale_count": ALevelCompetencyScale.objects.filter(policy__in=policy_qs).count(),
                "a_level_weight_count": ALevelComponentWeight.objects.filter(policy__in=policy_qs).count(),
                "a_level_module_count": ALevelSubjectModule.objects.filter(
                    subject__section__in=_secondary_sections_queryset(active_level=active_level)
                ).count(),
                "a_level_assessment_count": ALevelModuleAssessment.objects.filter(policy__in=policy_qs).count(),
                "recent_a_level_assessments": ALevelModuleAssessment.objects.select_related(
                    "student", "subject", "module"
                ).order_by("-assessed_on", "-id")[:8],
            }
        )
    else:
        context.update(
            {
                "task_count": ContinuousAssessmentTask.objects.count(),
                "pending_records_count": ContinuousAssessmentRecord.objects.filter(
                    moderation_status=ContinuousAssessmentRecord.ModerationStatus.PENDING
                ).count(),
                "batch_count": UNEBSubmissionBatch.objects.count(),
                "pending_batch_count": UNEBSubmissionBatch.objects.filter(
                    status__in=[UNEBSubmissionBatch.Status.PENDING_APPROVAL, UNEBSubmissionBatch.Status.APPROVED]
                ).count(),
                "recent_tasks": ContinuousAssessmentTask.objects.select_related("subject", "academic_class").order_by(
                    "-id"
                )[:8],
            }
        )
    return render(request, "secondary/dashboard.html", context)


@login_required
@secondary_mode_required
def policy_list_view(request):
    active_level = get_active_school_level(request)
    policy_level = _current_policy_level(request)
    form = SecondaryComputationPolicyForm(
        request.POST or None,
        initial={"level": policy_level},
    )
    _bind_secondary_form_querysets(form, active_level=active_level)
    form.fields["level"].disabled = True
    form.fields["level"].help_text = "Level is driven by the active school mode."

    if request.method == "POST":
        if form.is_valid():
            policy = form.save(commit=False)
            policy.level = policy_level
            policy.save()
            messages.success(request, "Secondary computation policy saved.")
            return redirect("secondary:policy_list")
        messages.error(request, "Failed to save policy. Review the form and try again.")

    policies = SecondaryComputationPolicy.objects.select_related("section").annotate(
        grade_band_count=Count("grade_bands")
    ).filter(level=policy_level).order_by("-effective_from", "-id")
    return render(
        request,
        "secondary/policies.html",
        {
            "form": form,
            "policies": policies,
            "policy_level": policy_level,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def policy_grade_bands_view(request, policy_id):
    policy = get_object_or_404(SecondaryComputationPolicy, id=policy_id)
    if policy.level != SecondaryComputationPolicy.Level.LOWER_SECONDARY:
        messages.error(request, "Grade bands are only used for lower secondary policies.")
        return redirect("secondary:policy_list")
    form = SecondaryGradeBandForm(request.POST or None)

    if request.method == "POST":
        delete_band_id = request.POST.get("delete_band_id")
        if delete_band_id:
            policy.grade_bands.filter(id=delete_band_id).delete()
            messages.success(request, "Grade band removed.")
            return redirect("secondary:policy_grade_bands", policy_id=policy.id)

        if form.is_valid():
            grade_band = form.save(commit=False)
            grade_band.policy = policy
            grade_band.save()
            messages.success(request, "Grade band saved.")
            return redirect("secondary:policy_grade_bands", policy_id=policy.id)
        messages.error(request, "Failed to save grade band.")

    return render(
        request,
        "secondary/policy_grade_bands.html",
        {
            "policy": policy,
            "bands": policy.grade_bands.all(),
            "form": form,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def competency_list_view(request):
    form = SecondaryCompetencyForm(request.POST or None)
    if request.method == "POST":
        delete_id = request.POST.get("delete_competency_id")
        if delete_id:
            get_object_or_404(form._meta.model, id=delete_id).delete()
            messages.success(request, "Competency removed.")
            return redirect("secondary:competency_list")

        if form.is_valid():
            form.save()
            messages.success(request, "Competency saved.")
            return redirect("secondary:competency_list")
        messages.error(request, "Failed to save competency.")

    return render(
        request,
        "secondary/competencies.html",
        {
            "form": form,
            "competencies": form._meta.model.objects.all().order_by("code"),
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def subject_competency_list_view(request):
    form = SubjectCompetencyForm(request.POST or None)
    _bind_secondary_form_querysets(form, active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER)

    if request.method == "POST":
        delete_id = request.POST.get("delete_subject_competency_id")
        if delete_id:
            get_object_or_404(form._meta.model, id=delete_id).delete()
            messages.success(request, "Subject competency removed.")
            return redirect("secondary:subject_competency_list")

        if form.is_valid():
            subject_competency = form.save(commit=False)
            subject_competency.section = subject_competency.subject.section
            subject_competency.save()
            messages.success(request, "Subject competency saved.")
            return redirect("secondary:subject_competency_list")
        messages.error(request, "Failed to save subject competency.")

    subject_competencies = form._meta.model.objects.select_related("subject", "competency", "section").order_by(
        "section__section_name", "subject__name", "competency__code"
    )
    return render(
        request,
        "secondary/subject_competencies.html",
        {
            "form": form,
            "subject_competencies": subject_competencies,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def ca_task_list_view(request):
    form = ContinuousAssessmentTaskForm(request.POST or None)
    _bind_secondary_form_querysets(form, active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER)

    class_id = request.GET.get("class_id")
    subject_id = request.GET.get("subject_id")
    status = request.GET.get("status")

    tasks = ContinuousAssessmentTask.objects.select_related(
        "academic_class__Class", "academic_class__term", "subject", "term"
    )
    if class_id:
        tasks = tasks.filter(academic_class_id=class_id)
    if subject_id:
        tasks = tasks.filter(subject_id=subject_id)
    if status:
        tasks = tasks.filter(status=status)

    if request.method == "POST":
        if form.is_valid():
            task = form.save(commit=False)
            task.created_by = request.user
            task.save()
            messages.success(request, "Continuous assessment task created.")
            return redirect("secondary:ca_task_list")
        messages.error(request, "Failed to create task.")

    return render(
        request,
        "secondary/ca_tasks.html",
        {
            "form": form,
            "tasks": tasks.order_by("-assigned_date", "-id"),
            "class_options": _secondary_academic_classes_queryset(
                active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER
            ),
            "subject_options": _secondary_subjects_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER),
            "status_options": ContinuousAssessmentTask.Status.choices,
            "selected_class_id": class_id or "",
            "selected_subject_id": subject_id or "",
            "selected_status": status or "",
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def ca_task_records_view(request, task_id):
    task = get_object_or_404(
        ContinuousAssessmentTask.objects.select_related("academic_class__Class", "subject", "term"),
        id=task_id,
    )

    student_ids = list(
        ClassRegister.objects.filter(
            academic_class_stream__academic_class=task.academic_class
        ).values_list("student_id", flat=True)
    )
    students = Student.objects.filter(id__in=student_ids).order_by("student_name")
    if not students.exists():
        students = Student.objects.filter(
            current_class=task.academic_class.Class,
            term=task.term,
            is_active=True,
        ).order_by("student_name")

    if request.method == "POST":
        action = request.POST.get("action", "save_records")
        saved_count = 0

        for student in students:
            score_raw = (request.POST.get(f"score_{student.id}") or "").strip()
            comment = (request.POST.get(f"comment_{student.id}") or "").strip()
            evidence = (request.POST.get(f"evidence_{student.id}") or "").strip()

            record = task.records.filter(student=student).first()
            if record and record.is_locked:
                continue
            if not score_raw:
                continue

            try:
                score = Decimal(score_raw)
            except (InvalidOperation, ValueError):
                messages.error(request, f"Invalid score for {student.student_name}.")
                continue

            if record:
                record.raw_score = score
                record.teacher_comment = comment
                record.evidence_reference = evidence
                record.updated_by = request.user
                record.save()
                saved_count += 1
            else:
                ContinuousAssessmentRecord.objects.create(
                    task=task,
                    student=student,
                    raw_score=score,
                    teacher_comment=comment,
                    evidence_reference=evidence,
                    moderation_status=ContinuousAssessmentRecord.ModerationStatus.DRAFT,
                    entered_by=request.user,
                    updated_by=request.user,
                )
                saved_count += 1

        if action == "submit_for_moderation":
            updated = task.records.filter(
                is_locked=False,
                moderation_status=ContinuousAssessmentRecord.ModerationStatus.DRAFT,
            ).update(moderation_status=ContinuousAssessmentRecord.ModerationStatus.PENDING)
            if updated:
                task.status = ContinuousAssessmentTask.Status.PENDING_MODERATION
                task.save(update_fields=["status", "updated_at"])
            messages.success(request, f"Saved {saved_count} records and sent {updated} for moderation.")
        else:
            messages.success(request, f"Saved {saved_count} records.")

        return redirect("secondary:ca_task_records", task_id=task.id)

    records_map = {record.student_id: record for record in task.records.select_related("student")}
    return render(
        request,
        "secondary/ca_task_records.html",
        {
            "task": task,
            "students": students,
            "records_map": records_map,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def moderation_queue_view(request):
    records = ContinuousAssessmentRecord.objects.select_related(
        "student", "task", "task__subject", "task__academic_class__Class", "task__term"
    ).order_by("-task__assigned_date", "task__subject__name", "student__student_name")

    selected_status = request.GET.get("status")
    if selected_status:
        records = records.filter(moderation_status=selected_status)
    else:
        records = records.filter(
            moderation_status__in=[
                ContinuousAssessmentRecord.ModerationStatus.PENDING,
                ContinuousAssessmentRecord.ModerationStatus.MODERATED,
                ContinuousAssessmentRecord.ModerationStatus.REJECTED,
            ]
        )

    if request.method == "POST":
        record_id = request.POST.get("record_id")
        moderation_status = request.POST.get("moderation_status", CAModeration.Status.ADJUSTED)
        moderated_score_raw = (request.POST.get("moderated_score") or "").strip()
        comments = (request.POST.get("comments") or "").strip()
        record = get_object_or_404(ContinuousAssessmentRecord, id=record_id)

        if record.is_locked:
            messages.error(request, "This record is locked and cannot be moderated.")
            return redirect("secondary:moderation_queue")

        moderated_score = None
        if moderated_score_raw:
            try:
                moderated_score = Decimal(moderated_score_raw)
            except (InvalidOperation, ValueError):
                messages.error(request, "Invalid moderated score.")
                return redirect("secondary:moderation_queue")

        if moderated_score is not None:
            record.moderated_score = moderated_score

        if moderation_status == CAModeration.Status.APPROVED:
            record.moderation_status = ContinuousAssessmentRecord.ModerationStatus.APPROVED
        elif moderation_status == CAModeration.Status.REJECTED:
            record.moderation_status = ContinuousAssessmentRecord.ModerationStatus.REJECTED
        else:
            record.moderation_status = ContinuousAssessmentRecord.ModerationStatus.MODERATED
        record.updated_by = request.user
        record.save()

        CAModeration.objects.update_or_create(
            record=record,
            defaults={
                "status": moderation_status,
                "moderated_score": moderated_score,
                "comments": comments,
                "moderated_by": request.user,
            },
        )
        _refresh_task_status(record.task)
        messages.success(request, "Moderation update saved.")
        return redirect("secondary:moderation_queue")

    return render(
        request,
        "secondary/moderation_queue.html",
        {
            "records": records,
            "selected_status": selected_status or "",
            "status_choices": ContinuousAssessmentRecord.ModerationStatus.choices,
            "moderation_action_choices": CAModeration.Status.choices,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def uneb_batch_list_view(request):
    form = UNEBSubmissionBatchForm(request.POST or None)
    _bind_secondary_form_querysets(form, active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER)

    if request.method == "POST":
        if form.is_valid():
            batch = form.save(commit=False)
            batch.created_by = request.user
            batch.save()
            messages.success(request, "UNEB CA batch created.")
            return redirect("secondary:uneb_batch_detail", batch_id=batch.id)
        messages.error(request, "Failed to create UNEB CA batch.")

    batches = UNEBSubmissionBatch.objects.select_related(
        "academic_year", "section", "candidate_class"
    ).annotate(item_count=Count("items")).order_by("-created_at")
    return render(
        request,
        "secondary/uneb_batches.html",
        {
            "form": form,
            "batches": batches,
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def uneb_batch_detail_view(request, batch_id):
    batch = get_object_or_404(
        UNEBSubmissionBatch.objects.select_related("academic_year", "section", "candidate_class"),
        id=batch_id,
    )

    if request.method == "POST":
        action = request.POST.get("action")
        if action == "approve_batch":
            if not batch.items.exists():
                messages.error(request, "Build batch items before approval.")
            else:
                batch.status = UNEBSubmissionBatch.Status.APPROVED
                batch.approved_by = request.user
                batch.approved_at = timezone.now()
                batch.save(update_fields=["status", "approved_by", "approved_at", "updated_at"])
                messages.success(request, "Batch approved.")
            return redirect("secondary:uneb_batch_detail", batch_id=batch.id)

        if action == "submit_batch":
            if batch.status not in {UNEBSubmissionBatch.Status.APPROVED, UNEBSubmissionBatch.Status.SUBMITTED}:
                messages.error(request, "Batch must be approved before submission.")
            else:
                batch.status = UNEBSubmissionBatch.Status.SUBMITTED
                batch.submitted_by = request.user
                batch.submitted_at = timezone.now()
                batch.save(update_fields=["status", "submitted_by", "submitted_at", "updated_at"])
                messages.success(request, "Batch marked as submitted.")
            return redirect("secondary:uneb_batch_detail", batch_id=batch.id)

    items = batch.items.select_related("student", "subject").order_by("student__student_name", "subject__name")
    return render(
        request,
        "secondary/uneb_batch_detail.html",
        {
            "batch": batch,
            "items": items,
            "candidate_classes": _secondary_classes_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER),
        },
    )


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def uneb_batch_build_items_view(request, batch_id):
    batch = get_object_or_404(UNEBSubmissionBatch, id=batch_id)
    class_id = request.POST.get("candidate_class_id") or batch.candidate_class_id
    if not class_id:
        messages.error(request, "Select a candidate class before building batch items.")
        return redirect("secondary:uneb_batch_detail", batch_id=batch.id)

    candidate_class = get_object_or_404(Class, id=class_id, section=batch.section)
    if batch.candidate_class_id != candidate_class.id:
        batch.candidate_class = candidate_class
        batch.save(update_fields=["candidate_class", "updated_at"])

    students = Student.objects.filter(
        current_class=candidate_class,
        current_class__section=batch.section,
        is_active=True,
    ).order_by("student_name")
    if not students.exists():
        student_ids = ClassRegister.objects.filter(
            academic_class_stream__academic_class__Class=candidate_class,
            academic_class_stream__academic_class__academic_year=batch.academic_year,
        ).values_list("student_id", flat=True)
        students = Student.objects.filter(id__in=student_ids).order_by("student_name")

    subjects = Subject.objects.filter(section=batch.section).order_by("name")
    items = build_uneb_batch_items(batch=batch, students=students, subjects=subjects)
    if items and batch.status == UNEBSubmissionBatch.Status.DRAFT:
        batch.status = UNEBSubmissionBatch.Status.PENDING_APPROVAL
        batch.save(update_fields=["status", "updated_at"])
    messages.success(request, f"Batch refreshed with {len(items)} subject entries.")
    return redirect("secondary:uneb_batch_detail", batch_id=batch.id)


@login_required
@secondary_mode_required
@secondary_lower_mode_required
def uneb_batch_lock_view(request, batch_id):
    batch = get_object_or_404(UNEBSubmissionBatch, id=batch_id)
    if request.method != "POST":
        return redirect("secondary:uneb_batch_detail", batch_id=batch.id)

    if batch.status not in {UNEBSubmissionBatch.Status.APPROVED, UNEBSubmissionBatch.Status.SUBMITTED, UNEBSubmissionBatch.Status.LOCKED}:
        messages.error(request, "Approve and submit batch before locking.")
        return redirect("secondary:uneb_batch_detail", batch_id=batch.id)

    lock_uneb_batch(batch, user=request.user)
    messages.success(request, "Batch locked. UNEB CA and related records are now immutable.")
    return redirect("secondary:uneb_batch_detail", batch_id=batch.id)


@login_required
@secondary_mode_required
def report_preview_view(request):
    if get_active_school_level(request) == SchoolSetting.EducationLevel.SECONDARY_UPPER:
        return redirect("secondary:alevel_report_preview")

    sections = _secondary_sections_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER)
    student_qs = _secondary_students_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_LOWER)
    subject_qs = Subject.objects.filter(section__in=sections).order_by("name")
    policy_qs = SecondaryComputationPolicy.objects.filter(
        section__in=sections,
        is_active=True,
        level=SecondaryComputationPolicy.Level.LOWER_SECONDARY,
    ).order_by("-effective_from", "name")
    form = SecondaryResultPreviewForm(
        request.POST or None,
        student_qs=student_qs,
        subject_qs=subject_qs,
        policy_qs=policy_qs,
    )

    result = None
    ca_records = []
    if request.method == "POST" and form.is_valid():
        student = form.cleaned_data["student"]
        subject = form.cleaned_data["subject"]
        exam_score = form.cleaned_data["exam_score"]
        policy = form.cleaned_data.get("policy")
        try:
            result = compute_subject_result(student=student, subject=subject, exam_score=exam_score, policy=policy)
            ca_records = ContinuousAssessmentRecord.objects.filter(
                student=student,
                task__subject=subject,
            ).select_related("task").order_by("task__assigned_date")
        except ValueError as exc:
            messages.error(request, str(exc))

    return render(
        request,
        "secondary/report_preview.html",
        {
            "form": form,
            "result": result,
            "ca_records": ca_records,
        },
    )


def _upper_policy_queryset():
    return SecondaryComputationPolicy.objects.filter(
        section__in=_secondary_sections_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER),
        level=SecondaryComputationPolicy.Level.UPPER_SECONDARY,
        is_active=True,
    ).order_by("-effective_from", "-id")


def _selected_object_from_request(request, queryset, key):
    obj_id = request.POST.get(key) or request.GET.get(key)
    if not obj_id:
        return queryset.first()
    try:
        return queryset.filter(id=int(obj_id)).first() or queryset.first()
    except (TypeError, ValueError):
        return queryset.first()


@login_required
@secondary_mode_required
@secondary_upper_mode_required
def alevel_scales_view(request):
    policy_qs = _upper_policy_queryset()
    selected_policy = _selected_object_from_request(request, policy_qs, "policy_id")

    form = ALevelCompetencyScaleForm(request.POST or None, policy_qs=policy_qs)
    if selected_policy and request.method != "POST":
        form.fields["policy"].initial = selected_policy.id

    if request.method == "POST":
        delete_id = request.POST.get("delete_scale_id")
        if delete_id:
            scale = get_object_or_404(ALevelCompetencyScale, id=delete_id, policy__in=policy_qs)
            selected_policy_id = scale.policy_id
            scale.delete()
            messages.success(request, "A-Level competency scale removed.")
            return redirect(f"{reverse('secondary:alevel_scales')}?policy_id={selected_policy_id}")

        if form.is_valid():
            scale = form.save()
            messages.success(request, "A-Level competency scale saved.")
            return redirect(f"{reverse('secondary:alevel_scales')}?policy_id={scale.policy_id}")
        messages.error(request, "Failed to save A-Level competency scale.")

    scales = (
        ALevelCompetencyScale.objects.filter(policy=selected_policy).order_by("display_order", "-point_value")
        if selected_policy
        else ALevelCompetencyScale.objects.none()
    )
    return render(
        request,
        "secondary/alevel_scales.html",
        {
            "form": form,
            "policy_options": policy_qs,
            "selected_policy": selected_policy,
            "scales": scales,
        },
    )


@login_required
@secondary_mode_required
@secondary_upper_mode_required
def alevel_component_weights_view(request):
    policy_qs = _upper_policy_queryset()
    subject_qs = _secondary_subjects_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    selected_policy = _selected_object_from_request(request, policy_qs, "policy_id")

    form = ALevelComponentWeightForm(
        request.POST or None,
        policy_qs=policy_qs,
        subject_qs=subject_qs,
    )
    if selected_policy and request.method != "POST":
        form.fields["policy"].initial = selected_policy.id

    if request.method == "POST":
        delete_id = request.POST.get("delete_weight_id")
        if delete_id:
            weight = get_object_or_404(ALevelComponentWeight, id=delete_id, policy__in=policy_qs)
            selected_policy_id = weight.policy_id
            weight.delete()
            messages.success(request, "A-Level component weight removed.")
            return redirect(f"{reverse('secondary:alevel_component_weights')}?policy_id={selected_policy_id}")

        if form.is_valid():
            weight = form.save()
            messages.success(request, "A-Level component weight saved.")
            return redirect(f"{reverse('secondary:alevel_component_weights')}?policy_id={weight.policy_id}")
        messages.error(request, "Failed to save A-Level component weight.")

    weights = (
        ALevelComponentWeight.objects.filter(policy=selected_policy).select_related("subject").order_by(
            "subject__name", "component_type"
        )
        if selected_policy
        else ALevelComponentWeight.objects.none()
    )
    return render(
        request,
        "secondary/alevel_component_weights.html",
        {
            "form": form,
            "policy_options": policy_qs,
            "selected_policy": selected_policy,
            "weights": weights,
        },
    )


@login_required
@secondary_mode_required
@secondary_upper_mode_required
def alevel_modules_view(request):
    subject_qs = _secondary_subjects_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    selected_subject = _selected_object_from_request(request, subject_qs, "subject_id")
    form = ALevelSubjectModuleForm(request.POST or None, subject_qs=subject_qs)
    if selected_subject and request.method != "POST":
        form.fields["subject"].initial = selected_subject.id

    if request.method == "POST":
        delete_id = request.POST.get("delete_module_id")
        if delete_id:
            module = get_object_or_404(ALevelSubjectModule, id=delete_id, subject__in=subject_qs)
            selected_subject_id = module.subject_id
            module.delete()
            messages.success(request, "A-Level subject module removed.")
            return redirect(f"{reverse('secondary:alevel_modules')}?subject_id={selected_subject_id}")

        if form.is_valid():
            module = form.save()
            messages.success(request, "A-Level subject module saved.")
            return redirect(f"{reverse('secondary:alevel_modules')}?subject_id={module.subject_id}")
        messages.error(request, "Failed to save A-Level subject module.")

    modules = (
        ALevelSubjectModule.objects.filter(subject=selected_subject).order_by("module_order", "code")
        if selected_subject
        else ALevelSubjectModule.objects.none()
    )
    return render(
        request,
        "secondary/alevel_modules.html",
        {
            "form": form,
            "subject_options": subject_qs,
            "selected_subject": selected_subject,
            "modules": modules,
        },
    )


@login_required
@secondary_mode_required
@secondary_upper_mode_required
def alevel_assessments_view(request):
    policy_qs = _upper_policy_queryset()
    student_qs = _secondary_students_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    subject_qs = _secondary_subjects_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    module_qs = ALevelSubjectModule.objects.filter(subject__in=subject_qs).order_by("subject__name", "module_order")
    form = ALevelModuleAssessmentForm(
        request.POST or None,
        policy_qs=policy_qs,
        student_qs=student_qs,
        subject_qs=subject_qs,
        module_qs=module_qs,
    )

    if request.method == "POST":
        delete_id = request.POST.get("delete_assessment_id")
        if delete_id:
            assessment = get_object_or_404(ALevelModuleAssessment, id=delete_id, policy__in=policy_qs)
            assessment.delete()
            messages.success(request, "A-Level module assessment removed.")
            return redirect("secondary:alevel_assessments")

        if form.is_valid():
            assessment = form.save()
            messages.success(request, "A-Level module assessment saved.")
            return redirect(
                f"{reverse('secondary:alevel_assessments')}?student_id={assessment.student_id}&subject_id={assessment.subject_id}"
            )
        messages.error(request, "Failed to save A-Level module assessment.")

    selected_student = _selected_object_from_request(request, student_qs, "student_id")
    selected_subject = _selected_object_from_request(request, subject_qs, "subject_id")
    assessments = ALevelModuleAssessment.objects.filter(policy__in=policy_qs).select_related(
        "policy", "student", "subject", "module"
    )
    if selected_student:
        assessments = assessments.filter(student=selected_student)
    if selected_subject:
        assessments = assessments.filter(subject=selected_subject)
    assessments = assessments.order_by("-assessed_on", "student__student_name", "subject__name", "-attempt_no")

    return render(
        request,
        "secondary/alevel_assessments.html",
        {
            "form": form,
            "assessments": assessments[:150],
            "student_options": student_qs,
            "subject_options": subject_qs,
            "selected_student": selected_student,
            "selected_subject": selected_subject,
        },
    )


@login_required
@secondary_mode_required
@secondary_upper_mode_required
def alevel_report_preview_view(request):
    student_qs = _secondary_students_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    subject_qs = _secondary_subjects_queryset(active_level=SchoolSetting.EducationLevel.SECONDARY_UPPER)
    policy_qs = _upper_policy_queryset()

    form = ALevelResultPreviewForm(
        request.POST or None,
        student_qs=student_qs,
        subject_qs=subject_qs,
        policy_qs=policy_qs,
    )

    result = None
    selected_records = []
    component_rows = []
    if request.method == "POST" and form.is_valid():
        student = form.cleaned_data["student"]
        subject = form.cleaned_data["subject"]
        policy = form.cleaned_data.get("policy")
        try:
            result = compute_a_level_subject_result(student=student, subject=subject, policy=policy)
            selected_records = result.get("selected_records", [])
            for component_type, point_value in result.get("component_points", {}).items():
                if component_type.endswith("_weight") or component_type.endswith("_contribution"):
                    continue
                component_rows.append(
                    {
                        "component_type": component_type,
                        "point_value": point_value,
                        "weight": result["component_points"].get(f"{component_type}_weight", Decimal("0.00")),
                        "contribution": result["component_points"].get(
                            f"{component_type}_contribution",
                            Decimal("0.00"),
                        ),
                    }
                )
        except ValueError as exc:
            messages.error(request, str(exc))

    return render(
        request,
        "secondary/alevel_report_preview.html",
        {
            "form": form,
            "result": result,
            "selected_records": selected_records,
            "component_rows": component_rows,
        },
    )
