﻿from collections import defaultdict
from decimal import Decimal, InvalidOperation
from io import BytesIO
from django.contrib import messages
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User, Group
from django.db import transaction
from django.db.models import Sum
from django.http import FileResponse, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone

from .forms import (
    SchoolForm, ParticipantForm, ItemForm, PointRuleForm,
    AcademicYearForm, UserCreateForm, UserPasswordForm,
)
from .models import (
    School, Participant, ParticipantItem, Item, GroupEntry, GroupMember,
    PointRule, Result, SchoolPoint, ParticipantPoint, AcademicYear,
    SystemSetting, StageMark,
)
from .services import recompute_results, publish_item, reset_item

def group_exists(user, name):
    return user.groups.filter(name=name).exists()

def is_admin(user):
    return user.is_authenticated and user.is_superuser

def is_school_user(user):
    return user.is_authenticated and group_exists(user, "School User")

def is_result_user(user):
    return user.is_authenticated and group_exists(user, "Result Entry User")

def current_school(user):
    if is_school_user(user) and not is_admin(user):
        return School.objects.filter(code=user.username).first()
    return None

def require_admin(view):
    return user_passes_test(is_admin, login_url="/login/")(view)

def can_manage_school(user, school):
    return is_admin(user) or (is_school_user(user) and current_school(user) and current_school(user).id == school.id)

def school_locked(school):
    return bool(school and school.confirmed)

# ---------------- Authentication / Home ----------------
def home(request):
    return render(request, "home.html", {
        "school_count": School.objects.count(),
        "participant_count": Participant.objects.count(),
        "item_count": Item.objects.filter(active=True).count(),
        "published_count": Result.objects.filter(published=True).count(),
    })

def login_view(request):
    if request.user.is_authenticated:
        return redirect("home")
    if request.method == "POST":
        user = authenticate(
            request,
            username=request.POST.get("username", "").strip(),
            password=request.POST.get("password", ""),
        )
        if user:
            login(request, user)
            return redirect("home")
        messages.error(request, "Invalid username or password.")
    return render(request, "login.html")

def logout_view(request):
    logout(request)
    return redirect("home")

@login_required
def change_password(request):
    if request.method == "POST":
        old = request.POST.get("old_password", "")
        new = request.POST.get("new_password", "")
        confirm = request.POST.get("confirm_password", "")
        if not request.user.check_password(old):
            messages.error(request, "Current password is incorrect.")
        elif len(new) < 6:
            messages.error(request, "New password must contain at least 6 characters.")
        elif new != confirm:
            messages.error(request, "New passwords do not match.")
        else:
            request.user.set_password(new)
            request.user.save()
            messages.success(request, "Password changed. Please log in again.")
            logout(request)
            return redirect("login")
    return render(request, "change_password.html")

@login_required
def dashboard(request):
    return render(request, "dashboard.html")

# ---------------- School Management ----------------
@require_admin
def schools(request):
    if request.method == "POST":
        form = SchoolForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "School saved successfully.")
            return redirect("schools")
    else:
        form = SchoolForm()
    return render(request, "admin/schools.html", {
        "schools": School.objects.all(),
        "form": form,
    })

@require_admin
def school_edit(request, pk):
    school = get_object_or_404(School, pk=pk)
    form = SchoolForm(request.POST or None, instance=school)
    if form.is_valid():
        form.save()
        messages.success(request, "School updated successfully.")
        return redirect("schools")
    return render(request, "admin/form.html", {"form": form, "title": "Edit School"})

@require_admin
def school_delete(request, pk):
    school = get_object_or_404(School, pk=pk)
    if request.method == "POST":
        school.delete()
        messages.success(request, "School deleted.")
    return redirect("schools")

# ---------------- School Profile ----------------
@login_required
def profile(request):
    school = current_school(request.user)
    if not school:
        return redirect("home")
    form = SchoolForm(request.POST or None, instance=school)
    form.fields["name"].disabled = True
    form.fields["code"].disabled = True
    if form.is_valid() and not school_locked(school):
        form.save()
        messages.success(request, "Profile updated.")
        return redirect("profile")
    return render(request, "school/profile.html", {"form": form, "school": school})

# ---------------- Participants ----------------
@login_required
def participants(request):
    school = current_school(request.user)
    selected_id = request.GET.get("school")
    if is_admin(request.user):
        selected = School.objects.filter(pk=selected_id).first() if selected_id else None
    else:
        selected = school
    qs = Participant.objects.select_related("school")
    if selected:
        qs = qs.filter(school=selected)
    section_order = {"UP": 1, "HS": 2, "HSS": 3, "LP": 4}
    participants = list(qs)
    participants.sort(key=lambda p: (section_order.get(p.section, 9), p.sex, p.name.lower()))
    return render(request, "participants/list.html", {
        "participants": participants,
        "schools": School.objects.all(),
        "selected_school": selected,
        "locked": school_locked(selected) if selected else False,
    })

@login_required
def participant_add(request):
    if is_admin(request.user):
        school = School.objects.filter(pk=request.GET.get("school") or request.POST.get("school")).first()
    else:
        school = current_school(request.user)
    if not school:
        return redirect("participants")
    if school_locked(school) and not is_admin(request.user):
        messages.error(request, "School data is confirmed. Admin must reset the confirmation.")
        return redirect("participants")
    form = ParticipantForm(request.POST or None)
    if form.is_valid():
        p = form.save(commit=False)
        p.school = school
        p.save()
        messages.success(request, "Participant added.")
        return redirect(f"/participants/?school={school.id}" if is_admin(request.user) else "participants")
    return render(request, "form.html", {"form": form, "title": "Add Participant", "school": school})

@login_required
def participant_edit(request, pk):
    p = get_object_or_404(Participant.objects.select_related("school"), pk=pk)
    if not can_manage_school(request.user, p.school):
        return redirect("participants")
    if school_locked(p.school) and not is_admin(request.user):
        messages.error(request, "School data is confirmed.")
        return redirect("participants")
    form = ParticipantForm(request.POST or None, instance=p)
    if form.is_valid():
        form.save()
        messages.success(request, "Participant updated.")
        return redirect("participants")
    return render(request, "form.html", {"form": form, "title": "Edit Participant", "school": p.school})

@login_required
def participant_delete(request, pk):
    p = get_object_or_404(Participant.objects.select_related("school"), pk=pk)
    if request.method == "POST" and can_manage_school(request.user, p.school):
        if not school_locked(p.school) or is_admin(request.user):
            p.delete()
            messages.success(request, "Participant deleted.")
    return redirect("participants")

# ---------------- Participant Items ----------------
@login_required
def participant_items(request):

    school = current_school(request.user)

    # Admin can select any school.
    if is_admin(request.user):
        selected_school = (
            School.objects.filter(pk=request.GET.get("school")).first()
            if request.GET.get("school")
            else None
        )
    else:
        selected_school = school

    # --------------------------------------------------------
    # SAVE ITEMS
    # --------------------------------------------------------
    if request.method == "POST":

        participant_id = request.POST.get("participant_id")

        participant = Participant.objects.select_related(
            "school"
        ).filter(pk=participant_id).first()

        if not participant:
            messages.error(request, "Participant not found.")
            return redirect("participant_items")

        # Security: user can manage only the permitted school.
        if not can_manage_school(request.user, participant.school):
            messages.error(request, "You are not allowed to change this participant.")
            return redirect("participant_items")

        # Confirmed school cannot be edited by School User.
        if (
            school_locked(participant.school)
            and not is_admin(request.user)
        ):
            messages.error(
                request,
                "School data is confirmed. Editing is locked."
            )
            return redirect("participant_items")

        selected_ids = []

        for field in [
            "item1",
            "item2",
            "item3",
            "item4",
            "item5",
        ]:
            value = request.POST.get(field, "").strip()

            if value and value not in selected_ids:
                selected_ids.append(value)

        # Maximum 5 items.
        if len(selected_ids) > 5:
            messages.error(
                request,
                "Maximum 5 items are allowed for one participant."
            )
            return redirect(
                f"/participant-items/?school={participant.school_id}"
            )

        # ----------------------------------------------------
        # IMPORTANT:
        # ONLY ITEMS BELONGING TO THE PARTICIPANT'S SECTION
        # CAN BE SAVED.
        # ----------------------------------------------------
        allowed_items = Item.objects.filter(
            active=True,
            section=participant.section
        )

        allowed_ids = {
            str(item.id)
            for item in allowed_items
        }

        invalid_items = [
            item_id
            for item_id in selected_ids
            if item_id not in allowed_ids
        ]

        if invalid_items:
            messages.error(
                request,
                "One or more selected items do not belong to the participant's section."
            )
            return redirect(
                f"/participant-items/?school={participant.school_id}"
            )

        # Replace the participant's previous assignments.
        participant.item_links.all().delete()

        for item_id in selected_ids:
            ParticipantItem.objects.create(
                participant=participant,
                item_id=int(item_id)
            )

        messages.success(
            request,
            f"Items updated successfully for {participant.name}."
        )

        return redirect(
            f"/participant-items/?school={participant.school_id}"
        )

    # --------------------------------------------------------
    # DISPLAY PARTICIPANTS
    # --------------------------------------------------------
    qs = (
        Participant.objects
        .select_related("school")
        .prefetch_related("item_links__item")
    )

    if selected_school:
        qs = qs.filter(school=selected_school)

    participants = list(
        qs.order_by("section", "sex", "name")
    )

    # Section order.
    section_order = {
        "UP": 1,
        "HS": 2,
        "HSS": 3,
        "LP": 4,
    }

    participants.sort(
        key=lambda p: (
            section_order.get(p.section, 99),
            p.sex,
            p.name.lower()
        )
    )

    # --------------------------------------------------------
    # BUILD DATA FOR EACH PARTICIPANT
    # --------------------------------------------------------
    rows = []

    for participant in participants:

        section_items = list(
            Item.objects.filter(
                active=True,
                section=participant.section
            ).order_by("code")
        )

        assigned = list(
            participant.item_links.select_related(
                "item"
            ).order_by("item__code")
        )

        assigned_ids = [
            item.item_id
            for item in assigned[:5]
        ]

        rows.append({
            "participant": participant,
            "section_items": section_items,
            "assigned": assigned[:5],
            "assigned_ids": assigned_ids,
        })

    return render(
        request,
        "participants/items.html",
        {
            "rows": rows,
            "schools": School.objects.all(),
            "selected_school": selected_school,
        }
    )
@login_required
def participant_item_edit(request, pk):
    p = get_object_or_404(Participant.objects.select_related("school"), pk=pk)
    if not can_manage_school(request.user, p.school):
        return redirect("participant_items")
    if school_locked(p.school) and not is_admin(request.user):
        messages.error(request, "School data is confirmed.")
        return redirect("participant_items")
    items = Item.objects.filter(active=True, section=p.section).order_by("code")
    selected = set(p.item_links.values_list("item_id", flat=True))
    if request.method == "POST":
        chosen = request.POST.getlist("items")
        if len(chosen) > 5:
            messages.error(request, "Maximum 5 items are allowed per participant.")
        else:
            p.item_links.all().delete()
            for item_id in chosen:
                item = get_object_or_404(items, pk=item_id)
                p.item_links.create(item=item)
            messages.success(request, "Participant items updated.")
            return redirect("participant_items")
    return render(request, "participants/item_edit.html", {
        "participant": p, "items": items, "selected": selected
    })

# ---------------- Group Captains ----------------
@login_required
def captains(request):
    school = current_school(request.user)
    selected = School.objects.filter(pk=request.GET.get("school")).first() if is_admin(request.user) else school
    group_items = Item.objects.filter(active=True, participation_type="G").order_by("section", "code")
    qs = GroupEntry.objects.select_related("school", "item", "captain").prefetch_related("members__participant")
    if selected:
        qs = qs.filter(school=selected)
    if request.method == "POST" and selected and (is_admin(request.user) or not school_locked(selected)):
        item = get_object_or_404(group_items, pk=request.POST.get("item_id"))
        captain = get_object_or_404(Participant, pk=request.POST.get("captain_id"), school=selected)
        entry, _ = GroupEntry.objects.get_or_create(school=selected, item=item, defaults={"captain": captain})
        if entry.captain_id != captain.id:
            entry.captain = captain
            entry.save(update_fields=["captain"])
        messages.success(request, "Group captain saved.")
        return redirect("captains")
    captains = list(qs)
    return render(request, "participants/captains.html", {
        "captains": captains,
        "group_items": group_items,
        "participants": Participant.objects.filter(school=selected) if selected else Participant.objects.none(),
        "schools": School.objects.all(),
        "selected_school": selected,
    })

@login_required
def group_members(request, entry_id):
    entry = get_object_or_404(GroupEntry.objects.select_related("school", "item"), pk=entry_id)
    if not can_manage_school(request.user, entry.school):
        return redirect("captains")
    if school_locked(entry.school) and not is_admin(request.user):
        messages.error(request, "School data is confirmed.")
        return redirect("captains")
    members = list(entry.members.values_list("participant_id", flat=True))
    if request.method == "POST":
        selected = [int(x) for x in request.POST.getlist("members") if x.isdigit()]
        if len(selected) < entry.item.min_participants or len(selected) > entry.item.max_participants:
            messages.error(request, f"Participants must be between {entry.item.min_participants} and {entry.item.max_participants}.")
        else:
            entry.members.all().delete()
            for pid in selected:
                entry.members.create(participant_id=pid)
            messages.success(request, "Group members saved.")
            return redirect("captains")
    return render(request, "participants/group_members.html", {
        "entry": entry,
        "participants": Participant.objects.filter(school=entry.school).order_by("section", "sex", "name"),
        "members": set(members),
    })

# ---------------- Confirmation ----------------
@login_required
def confirmation(request):
    school = current_school(request.user)
    if not school:
        return redirect("home")
    if request.method == "POST" and not school.confirmed:
        password = request.POST.get("password", "")
        if request.user.check_password(password):
            school.confirmed = True
            school.confirmed_at = timezone.now()
            school.save(update_fields=["confirmed", "confirmed_at"])
            messages.success(request, "School data confirmed and locked.")
        else:
            messages.error(request, "Incorrect password.")
    return render(request, "school/confirmation.html", {"school": school})

@require_admin
def confirmation_status(request):
    if request.method == "POST":
        action = request.POST.get("action")
        school = get_object_or_404(School, pk=request.POST.get("school_id"))
        if action == "reset":
            school.confirmed = False
            school.confirmed_at = None
            school.save(update_fields=["confirmed", "confirmed_at"])
        elif action == "confirm":
            school.confirmed = True
            school.confirmed_at = timezone.now()
            school.save(update_fields=["confirmed", "confirmed_at"])
        messages.success(request, f"Confirmation updated for {school.name}.")
    return render(request, "admin/confirmation_status.html", {"schools": School.objects.all()})

# ---------------- Admin settings ----------------
@require_admin
def point_distribution(request):
    items = Item.objects.filter(active=True).order_by("section", "code")
    if request.method == "POST":
        for item in items:
            rule, _ = PointRule.objects.get_or_create(
                item=item,
                defaults={"first": 10 if item.participation_type == "G" else 5,
                          "second": 6 if item.participation_type == "G" else 3,
                          "third": 3 if item.participation_type == "G" else 1},
            )
            rule.first = int(request.POST.get(f"first_{item.id}", rule.first))
            rule.second = int(request.POST.get(f"second_{item.id}", rule.second))
            rule.third = int(request.POST.get(f"third_{item.id}", rule.third))
            rule.save()
        messages.success(request, "Point distribution updated.")
        return redirect("point_distribution")
    for item in items:
        PointRule.objects.get_or_create(
            item=item,
            defaults={"first": 10 if item.participation_type == "G" else 5,
                      "second": 6 if item.participation_type == "G" else 3,
                      "third": 3 if item.participation_type == "G" else 1},
        )
    return render(request, "admin/points.html", {"items": items})

@require_admin
def maximum_participation(request):
    items = Item.objects.filter(participation_type="G", active=True).order_by("section", "code")
    if request.method == "POST":
        item = get_object_or_404(Item, pk=request.POST.get("item_id"))
        item.min_participants = max(1, int(request.POST.get("min_participants", item.min_participants)))
        item.max_participants = max(item.min_participants, int(request.POST.get("max_participants", item.max_participants)))
        item.save()
        messages.success(request, "Maximum participation updated.")
        return redirect("maximum_participation")
    settings = {
        "individual_per_student": SystemSetting.get_int("individual_per_student", 5),
        "group_per_student": SystemSetting.get_int("group_per_student", 3),
    }
    return render(request, "admin/maximum.html", {"items": items, "settings": settings})

@require_admin
def save_general_limits(request):
    if request.method == "POST":
        for key in ["individual_per_student", "group_per_student"]:
            value = request.POST.get(key)
            if value and value.isdigit():
                SystemSetting.objects.update_or_create(key=key, defaults={"value": value})
        messages.success(request, "Participation limits saved.")
    return redirect("maximum_participation")

@require_admin
def academic_year(request):
    form = AcademicYearForm(request.POST or None)
    if form.is_valid():
        form.save()
        messages.success(request, "Academic year saved.")
        return redirect("academic_year")
    return render(request, "admin/academic_year.html", {"form": form, "years": AcademicYear.objects.all()})

# ---------------- Items (admin) ----------------
@require_admin
def items(request):
    form = ItemForm(request.POST or None)
    if form.is_valid():
        item = form.save()
        PointRule.objects.get_or_create(
            item=item,
            defaults={"first": 10 if item.participation_type == "G" else 5,
                      "second": 6 if item.participation_type == "G" else 3,
                      "third": 3 if item.participation_type == "G" else 1},
        )
        messages.success(request, "Item saved.")
        return redirect("items")
    return render(request, "admin/items.html", {"items": Item.objects.all(), "form": form})

@require_admin
def item_edit(request, pk):
    item = get_object_or_404(Item, pk=pk)
    form = ItemForm(request.POST or None, instance=item)
    if form.is_valid():
        form.save()
        messages.success(request, "Item updated.")
        return redirect("items")
    return render(request, "admin/form.html", {"form": form, "title": "Edit Item"})

@require_admin
def item_delete(request, pk):
    item = get_object_or_404(Item, pk=pk)
    if request.method == "POST":
        item.delete()
        messages.success(request, "Item deleted.")
    return redirect("items")

# ---------------- Chest numbers ----------------
@require_admin
def distribute_chest(request):
    if request.method == "POST":
        if School.objects.filter(confirmed=False).exists():
            messages.error(request, "All schools must be confirmed before chest-number distribution.")
            return redirect("distribute_chest")
        last = Participant.objects.exclude(chest_no__isnull=True).order_by("-chest_no").values_list("chest_no", flat=True).first() or 99
        changed = 0
        for p in Participant.objects.filter(chest_no__isnull=True).order_by("school__code", "section", "sex", "name"):
            last += 1
            p.chest_no = last
            p.save(update_fields=["chest_no"])
            changed += 1
        messages.success(request, f"Chest numbers assigned to {changed} new participants.")
    return render(request, "admin/distribute_chest.html")


@login_required
def report_confirmation(request):
    if is_admin(request.user):
        schools = School.objects.all()
    elif is_school_user(request.user):
        school = current_school(request.user)
        schools = School.objects.filter(pk=school.pk) if school else School.objects.none()
    else:
        schools = School.objects.all()
    return render(request, "admin/confirmation_status.html", {"schools": schools})

# ---------------- User Management ----------------
@require_admin
def user_management(request):
    if request.method == "POST":
        action = request.POST.get("action")
        if action == "create":
            form = UserCreateForm(request.POST)
            if form.is_valid():
                data = form.cleaned_data
                user = User.objects.create_user(
                    username=data["username"],
                    password=data["password"],
                )
                role = data["role"]
                if role == "admin":
                    user.is_staff = True
                    user.is_superuser = True
                    user.save()
                else:
                    group_name = "School User" if role == "school" else "Result Entry User"
                    group, _ = Group.objects.get_or_create(name=group_name)
                    user.groups.add(group)
                messages.success(request, f"{data['username']} created.")
                return redirect("user_management")
        elif action == "password":
            form = UserPasswordForm(request.POST)
            if form.is_valid():
                target = form.cleaned_data["user"]
                target.set_password(form.cleaned_data["password"])
                target.save()
                messages.success(request, f"Password changed for {target.username}.")
                return redirect("user_management")
        elif action == "delete":
            target = get_object_or_404(User, pk=request.POST.get("user_id"))
            if target.id != request.user.id:
                target.delete()
                messages.success(request, "User deleted.")
            else:
                messages.error(request, "You cannot delete your own account.")
            return redirect("user_management")
    else:
        form = UserCreateForm()
    return render(request, "admin/user_management.html", {
        "form": form,
        "users": User.objects.prefetch_related("groups").order_by("username"),
    })

# ---------------- Public / authenticated Results ----------------
def _result_queryset(request, published_only=True):
    qs = Result.objects.select_related("item", "participant__school", "group_entry__school")
    if published_only:
        qs = qs.filter(published=True)
    school_id = request.GET.get("school")
    section = request.GET.get("section")
    item_id = request.GET.get("item")
    participant_id = request.GET.get("participant")
    if school_id:
        qs = qs.filter(
            participant__school_id=school_id
        ) if participant_id or section or item_id else qs.filter(
            participant__school_id=school_id
        )
        qs = qs | Result.objects.filter(group_entry__school_id=school_id, published=published_only).select_related("item","participant__school","group_entry__school") if published_only else qs | Result.objects.filter(group_entry__school_id=school_id)
    if section:
        qs = qs.filter(item__section=section)
    if item_id:
        qs = qs.filter(item_id=item_id)
    if participant_id:
        qs = qs.filter(participant_id=participant_id)
    return qs.distinct().order_by("item__section", "item__code", "position", "id")

def full_result(request):
    results = _result_queryset(request, published_only=True)
    return render(request, "results/full_result.html", {
        "results": results,
        "schools": School.objects.all(),
        "items": Item.objects.filter(active=True),
        "participants": Participant.objects.all(),
    })

def published_results(request):
    items = Item.objects.filter(active=True).order_by("section", "code")
    status = {i.id: i.results.filter(published=True).exists() for i in items}
    return render(request, "results/published.html", {"items": items, "status": status})

@login_required
def school_championship(request):
    rows = []
    for school in School.objects.all():
        total = school.point_rows.filter(result__published=True).aggregate(v=Sum("points"))["v"] or 0
        sections = {}
        for sec in ["HSS", "HS", "UP", "LP"]:
            sections[sec] = school.point_rows.filter(result__published=True, result__item__section=sec).aggregate(v=Sum("points"))["v"] or 0
        rows.append({"school": school, "total": total, **sections})
    order = request.GET.get("order", "-total")
    rows.sort(key=lambda x: x["total"], reverse=(order == "-total"))
    return render(request, "results/school_championship.html", {"rows": rows})

@login_required
def championship(request):
    people = Participant.objects.annotate(total_points=Sum("point_rows__points")).order_by("-total_points", "name")
    champions = {}
    for sec in ["LP", "UP", "HS", "HSS"]:
        champions[sec] = {
            "M": list(people.filter(section=sec, sex="M")[:3]),
            "F": list(people.filter(section=sec, sex="F")[:3]),
        }
    kalaprathibha = people.filter(sex="M", item_links__item__participation_type="I").distinct().first()
    kalathilakam = people.filter(sex="F", item_links__item__participation_type="I").distinct().first()
    return render(request, "results/championship.html", {
        "champions": champions,
        "kalaprathibha": kalaprathibha,
        "kalathilakam": kalathilakam,
    })

# ---------------- Result Entry ----------------
@login_required
def result_entry(request):
    if not (is_admin(request.user) or is_result_user(request.user)):
        return redirect("home")
    code = request.GET.get("item_code", "").strip()
    item = Item.objects.filter(code=code, active=True).first() if code else None
    results = []
    if item:
        if item.participation_type == "I":
            competitors = Participant.objects.filter(
                item_links__item=item
            ).distinct().order_by("chest_no", "name")
            for p in competitors:
                r, _ = Result.objects.get_or_create(item=item, participant=p)
                results.append(r)
        else:
            groups = GroupEntry.objects.filter(
                item=item
            ).select_related("school", "captain").prefetch_related("members")
            for entry in groups:
                r, _ = Result.objects.get_or_create(item=item, group_entry=entry)
                results.append(r)
        results = list(Result.objects.filter(item=item).select_related("participant__school", "group_entry__school").order_by("position", "id"))

    if request.method == "POST" and item:
        if Result.objects.filter(item=item, confirmed=True).exists() and request.POST.get("action") in {"save", "confirm"}:
            messages.error(request, "This result is confirmed. Use Result Reset first.")
            return redirect(f"/results/entry/?item_code={item.code}")
        for r in results:
            pid = r.participant_id
            prefix = f"row_{r.id}_"
            try:
                r.mark1 = Decimal(request.POST.get(prefix+"m1", "0") or "0")
                r.mark2 = Decimal(request.POST.get(prefix+"m2", "0") or "0")
                r.mark3 = Decimal(request.POST.get(prefix+"m3", "0") or "0")
            except InvalidOperation:
                r.mark1 = r.mark2 = r.mark3 = Decimal("0")
            r.total = r.mark1 + r.mark2 + r.mark3
            r.save()
        recompute_results(item)
        if request.POST.get("action") == "confirm":
            Result.objects.filter(item=item).update(confirmed=True)
            messages.success(request, "Result confirmed.")
        else:
            messages.success(request, "Result saved.")
        return redirect(f"/results/entry/?item_code={item.code}")

    return render(request, "results/entry.html", {
        "item": item,
        "results": results,
        "code": code,
    })

@login_required
def publish_result(request):
    if not (is_admin(request.user) or is_result_user(request.user)):
        return redirect("home")
    if request.method == "POST":
        item = get_object_or_404(Item, pk=request.POST.get("item_id"))
        if not Result.objects.filter(item=item, confirmed=True).exists():
            messages.error(request, "Confirm the result before publishing.")
        else:
            publish_item(item)
            messages.success(request, "Result published and points distributed.")
    return redirect("result_entry")

@login_required
def result_reset(request):
    if not is_admin(request.user):
        return redirect("home")
    if request.method == "POST":
        item = get_object_or_404(Item, pk=request.POST.get("item_id"))
        reset_item(item)
        messages.success(request, f"{item.code} reset.")
    return render(request, "results/reset.html", {"items": Item.objects.filter(active=True)})

# ---------------- Stage Report ----------------
@login_required
def stage_report(request):
    if not (is_admin(request.user) or is_result_user(request.user)):
        return redirect("home")
    items = Item.objects.filter(active=True).order_by("section", "code")
    item = items.filter(pk=request.GET.get("item_id")).first() if request.GET.get("item_id") else None
    competitors = []
    if item:
        if item.participation_type == "I":
            competitors = Participant.objects.filter(item_links__item=item).select_related("school").order_by("chest_no", "name")
        else:
            competitors = GroupEntry.objects.filter(item=item).select_related("school", "captain").order_by("school__code")
    return render(request, "reports/stage.html", {"items": items, "item": item, "competitors": competitors})

# ---------------- PDF ----------------
def pdf_response(filename, build_fn):
    buf = BytesIO()
    build_fn(buf)
    buf.seek(0)
    return FileResponse(buf, as_attachment=True, filename=filename)

@login_required
def participants_pdf(request):
    if not (is_admin(request.user) or is_school_user(request.user) or is_result_user(request.user)):
        return redirect("home")
    school = current_school(request.user)
    qs = Participant.objects.select_related("school").prefetch_related("item_links__item").order_by("school__code","section","sex","name")
    if school:
        qs = qs.filter(school=school)

    def build(buf):
        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4, landscape
        from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
        from reportlab.lib.styles import getSampleStyleSheet
        doc = SimpleDocTemplate(buf, pagesize=landscape(A4), rightMargin=18,leftMargin=18,topMargin=18,bottomMargin=18)
        styles = getSampleStyleSheet()
        story = [Paragraph("Marthoma School Kalolsavam - Participants", styles["Title"]), Spacer(1,8)]
        data = [["Chest","Admission","Participant","School","Section","Class","Age","Item1","Item2","Item3","Item4","Item5"]]
        for p in qs:
            names = [x.item.name for x in p.item_links.all()[:5]]
            names += [""] * (5-len(names))
            data.append([p.chest_no or "", p.admission_no, p.name, p.school.name, p.section, p.class_name, p.age, *names])
        table = Table(data, repeatRows=1)
        table.setStyle(TableStyle([
            ("GRID",(0,0),(-1,-1),0.35,colors.grey),
            ("BACKGROUND",(0,0),(-1,0),colors.HexColor("#dce6f1")),
            ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
        ]))
        story.append(table)
        doc.build(story)
    return pdf_response("participants.pdf", build)

@login_required
def results_pdf(request):
    results = _result_queryset(request, published_only=True)

    def build(buf):
        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4, landscape
        from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
        from reportlab.lib.styles import getSampleStyleSheet
        doc = SimpleDocTemplate(buf, pagesize=landscape(A4))
        styles = getSampleStyleSheet()
        story = [Paragraph("Marthoma School Kalolsavam - Published Results", styles["Title"]), Spacer(1,8)]
        data = [["Place","Chest","Participant / Group","School","Section","Item","Total"]]
        for r in results:
            chest = r.participant.chest_no if r.participant else ""
            data.append([r.place, chest, r.competitor_name, r.competitor_school.name if r.competitor_school else "", r.item.section, r.item.name, str(r.total)])
        table = Table(data, repeatRows=1)
        table.setStyle(TableStyle([
            ("GRID",(0,0),(-1,-1),0.35,colors.grey),
            ("BACKGROUND",(0,0),(-1,0),colors.HexColor("#dce6f1")),
            ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
        ]))
        story.append(table)
        doc.build(story)
    return pdf_response("published_results.pdf", build)

@login_required
def stage_pdf(request):
    if not (is_admin(request.user) or is_result_user(request.user)):
        return redirect("home")
    item = get_object_or_404(Item, pk=request.GET.get("item_id"))
    if item.participation_type == "I":
        competitors = list(Participant.objects.filter(item_links__item=item).select_related("school").order_by("chest_no","name"))
    else:
        competitors = list(GroupEntry.objects.filter(item=item).select_related("school","captain").order_by("school__code"))

    def build(buf):
        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4
        from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, PageBreak
        from reportlab.lib.styles import getSampleStyleSheet
        doc = SimpleDocTemplate(buf, pagesize=A4, rightMargin=18,leftMargin=18,topMargin=18,bottomMargin=18)
        styles = getSampleStyleSheet()
        story = []
        rows_per_judge_page = 15

        # Three judge copies on three pages.
        for judge in range(1,4):
            story += [
                Paragraph("MARTHOMA SCHOOL KALOLSAVAM", styles["Title"]),
                Paragraph(f"Item Code: {item.code} &nbsp;&nbsp;&nbsp; Item: {item.name}", styles["Normal"]),
                Paragraph(f"Judge {judge} Name: ________________________________", styles["Normal"]),
                Spacer(1,8),
            ]
            data = [["Sl No","Code No","Mark","Remark"]]
            for i in range(rows_per_judge_page):
                code = competitors[i].chest_no if i < len(competitors) and item.participation_type=="I" else (
                    competitors[i].captain.chest_no if i < len(competitors) else ""
                )
                data.append([str(i+1), str(code or ""), "", ""])
            table = Table(data, colWidths=[45,80,65,290], rowHeights=[24]+[28]*rows_per_judge_page)
            table.setStyle(TableStyle([
                ("GRID",(0,0),(-1,-1),0.6,colors.black),
                ("BACKGROUND",(0,0),(-1,0),colors.HexColor("#e8eef7")),
                ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
                ("VALIGN",(0,0),(-1,-1),"MIDDLE"),
            ]))
            story += [table, Spacer(1,12), Paragraph("Signature of the Judge: ________________________________", styles["Normal"])]
            if judge != 3:
                story.append(PageBreak())

        story.append(PageBreak())
        story += [
            Paragraph("STAGE SUMMARY SHEET", styles["Title"]),
            Paragraph(f"Item Code: {item.code} &nbsp;&nbsp;&nbsp; Item: {item.name}", styles["Normal"]),
            Spacer(1,8),
        ]
        data = [["Code No","Chest No","Judge1","Judge2","Judge3","Total","Place"]]
        for i, competitor in enumerate(competitors[:30], start=1):
            chest = competitor.chest_no if item.participation_type=="I" else competitor.captain.chest_no
            data.append([i, chest or "", "", "", "", "", ""])
        table = Table(data, colWidths=[55,65,70,70,70,70,55])
        table.setStyle(TableStyle([
            ("GRID",(0,0),(-1,-1),0.6,colors.black),
            ("BACKGROUND",(0,0),(-1,0),colors.HexColor("#e8eef7")),
            ("FONTNAME",(0,0),(-1,0),"Helvetica-Bold"),
        ]))
        story += [table, Spacer(1,12), Paragraph("Signature of Stage Manager: ________________________________", styles["Normal"])]
        doc.build(story)
    return pdf_response("stage_report.pdf", build)

# ---------------- Result points detail ----------------
@login_required
def point_detail(request, result_id):
    result = get_object_or_404(Result.objects.select_related("item","participant__school","group_entry__school"), pk=result_id)
    points = SchoolPoint.objects.filter(result=result)
    participant_points = ParticipantPoint.objects.filter(result=result)
    return render(request, "results/point_detail.html", {
        "result": result,
        "school_points": points,
        "participant_points": participant_points,
    })

@login_required
def report_participant_items(request):
    """
    Read-only Participant Item Report.

    Shows:
    Chest No
    Admission No
    Participant Name
    Section
    Item 1
    Item 2
    Item 3
    Item 4
    Item 5

    No Edit/Delete functions are provided here.
    """

    school = current_school(request.user)

    # Admin may select a school.
    if is_admin(request.user):

        selected_school = (
            School.objects.filter(
                pk=request.GET.get("school")
            ).first()
            if request.GET.get("school")
            else None
        )

    else:

        selected_school = school

    qs = (
        Participant.objects
        .select_related("school")
        .prefetch_related("item_links__item")
    )

    if selected_school:
        qs = qs.filter(school=selected_school)

    participants = list(
        qs.order_by(
            "section",
            "sex",
            "name"
        )
    )

    # Exact section order required for the report.
    section_order = {
        "LP": 1,
        "UP": 2,
        "HS": 3,
        "HSS": 4,
    }

    participants.sort(
        key=lambda p: (
            section_order.get(p.section, 99),
            p.sex,
            p.name.lower()
        )
    )

    report_rows = []

    for participant in participants:

        assigned_items = list(
            participant.item_links
            .select_related("item")
            .order_by("item__code")
        )[:5]

        item_names = [
            x.item.name
            for x in assigned_items
        ]

        while len(item_names) < 5:
            item_names.append("")

        report_rows.append({
            "participant": participant,
            "item1": item_names[0],
            "item2": item_names[1],
            "item3": item_names[2],
            "item4": item_names[3],
            "item5": item_names[4],
        })

    return render(
        request,
        "reports/participant_items.html",
        {
            "report_rows": report_rows,
            "schools": School.objects.all(),
            "selected_school": selected_school,
        }
    )


@login_required
def report_participant_items_pdf(request):

    if not (
        is_admin(request.user)
        or is_school_user(request.user)
        or is_result_user(request.user)
    ):
        return redirect("home")

    school = current_school(request.user)

    if is_admin(request.user):

        selected_school = (
            School.objects.filter(
                pk=request.GET.get("school")
            ).first()
            if request.GET.get("school")
            else None
        )

    else:

        selected_school = school

    qs = (
        Participant.objects
        .select_related("school")
        .prefetch_related("item_links__item")
    )

    if selected_school:
        qs = qs.filter(school=selected_school)

    participants = list(
        qs.order_by(
            "section",
            "sex",
            "name"
        )
    )

    section_order = {
        "LP": 1,
        "UP": 2,
        "HS": 3,
        "HSS": 4,
    }

    participants.sort(
        key=lambda p: (
            section_order.get(p.section, 99),
            p.sex,
            p.name.lower()
        )
    )

    def build_pdf(buffer):

        from reportlab.lib import colors
        from reportlab.lib.pagesizes import A4, landscape
        from reportlab.lib.styles import getSampleStyleSheet
        from reportlab.platypus import (
            SimpleDocTemplate,
            Table,
            TableStyle,
            Paragraph,
            Spacer,
        )

        document = SimpleDocTemplate(
            buffer,
            pagesize=landscape(A4),
            rightMargin=18,
            leftMargin=18,
            topMargin=18,
            bottomMargin=18,
        )

        styles = getSampleStyleSheet()

        story = []

        story.append(
            Paragraph(
                "MARTHOMA SCHOOL KALOLSAVAM",
                styles["Title"]
            )
        )

        story.append(
            Paragraph(
                "Participant Item Report",
                styles["Heading2"]
            )
        )

        if selected_school:
            story.append(
                Paragraph(
                    f"School: {selected_school.name}",
                    styles["Normal"]
                )
            )

        story.append(Spacer(1, 10))

        data = [[
            "Chest No",
            "Admission No",
            "Participant Name",
            "Section",
            "Item 1",
            "Item 2",
            "Item 3",
            "Item 4",
            "Item 5",
        ]]

        for participant in participants:

            assigned = list(
                participant.item_links
                .select_related("item")
                .order_by("item__code")
            )[:5]

            names = [
                x.item.name
                for x in assigned
            ]

            while len(names) < 5:
                names.append("")

            data.append([
                participant.chest_no or "",
                participant.admission_no,
                participant.name,
                participant.section,
                names[0],
                names[1],
                names[2],
                names[3],
                names[4],
            ])

        table = Table(
            data,
            repeatRows=1
        )

        table.setStyle(
            TableStyle([
                (
                    "GRID",
                    (0, 0),
                    (-1, -1),
                    0.4,
                    colors.grey
                ),
                (
                    "BACKGROUND",
                    (0, 0),
                    (-1, 0),
                    colors.HexColor("#dce6f1")
                ),
                (
                    "FONTNAME",
                    (0, 0),
                    (-1, 0),
                    "Helvetica-Bold"
                ),
                (
                    "VALIGN",
                    (0, 0),
                    (-1, -1),
                    "MIDDLE"
                ),
                (
                    "FONTSIZE",
                    (0, 0),
                    (-1, -1),
                    8
                ),
            ])
        )

        story.append(table)

        document.build(story)

    return pdf_response(
        "participant_item_report.pdf",
        build_pdf
    )

