import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireCampusInstitution } from "@/lib/auth";

type Params = { params: Promise<{ id: string }> };

/** Marks input grid for one exam: eligible students (enrolled in each
    paper's course for the exam's session) + every paper with existing marks.

    GET ?scheduleId=<id> → rows of a single paper (course-wise entry)
    GET ?studentId=<id>  → rows of a single student (student-wise entry) */
export async function GET(req: Request, { params }: Params) {
  const session = await requireCampusInstitution();
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { id } = await params;
  const exam = await prisma.cuExam.findFirst({
    where: { id: Number(id), institutionId: session.institutionId },
    select: {
      id: true,
      sessionYear: true,
      programId: true,
      resultPublished: true,
      schedules: {
        orderBy: [{ examDate: "asc" }, { startTime: "asc" }, { id: "asc" }],
        select: {
          id: true, examDate: true, startTime: true, endTime: true, room: true,
          fullMarks: true, passMarks: true,
          course: { select: { id: true, code: true, name: true, nameBn: true, credits: true } },
          marks: { select: { studentId: true, marks: true, absent: true } },
        },
      },
    },
  });
  if (!exam) return NextResponse.json({ error: "Not found" }, { status: 404 });

  // eligible students: active enrollments in offerings of the exam's session
  // & program (per course); union across papers
  const courseIds = exam.schedules.map((s) => s.course.id);
  const enrollments =
    courseIds.length === 0
      ? []
      : await prisma.cuEnrollment.findMany({
          where: {
            institutionId: session.institutionId,
            status: "ENROLLED",
            offering: {
              courseId: { in: courseIds },
              ...(exam.sessionYear ? { sessionYear: exam.sessionYear } : {}),
            },
            student: { status: "ACTIVE" },
          },
          select: {
            offering: { select: { courseId: true } },
            student: { select: { id: true, studentIdNo: true, name: true, nameBn: true, sessionYear: true } },
          },
        });

  // dedupe students; keep the set of courses each is enrolled in
  const map = new Map<number, { id: number; studentIdNo: string; name: string; nameBn: string | null; sessionYear: string; courseIds: number[] }>();
  for (const e of enrollments) {
    const s = map.get(e.student.id) ?? { ...e.student, courseIds: [] };
    if (!s.courseIds.includes(e.offering.courseId)) s.courseIds.push(e.offering.courseId);
    map.set(e.student.id, s);
  }
  const students = [...map.values()].sort((a, b) => a.studentIdNo.localeCompare(b.studentIdNo));

  return NextResponse.json({ exam: { id: exam.id, resultPublished: exam.resultPublished, sessionYear: exam.sessionYear }, schedules: exam.schedules, students });
}

/** Save marks: { rows: [{ scheduleId, studentId, marks?, absent?, note? }] } —
    upsert per (schedule, student); bounds come from the paper's full marks.
    Blocked while the result is published. */
export async function POST(req: Request, { params }: Params) {
  const session = await requireCampusInstitution();
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { id } = await params;
  const exam = await prisma.cuExam.findFirst({
    where: { id: Number(id), institutionId: session.institutionId },
    select: { id: true, resultPublished: true },
  });
  if (!exam) return NextResponse.json({ error: "Not found" }, { status: 404 });
  if (exam.resultPublished) {
    return NextResponse.json({ error: "RESULT_PUBLISHED" }, { status: 409 });
  }

  try {
    const body = await req.json();
    const rows: { scheduleId: number; studentId: number; marks?: unknown; absent?: unknown; note?: unknown }[] = Array.isArray(body.rows) ? body.rows : [];
    if (rows.length === 0) return NextResponse.json({ error: "No rows" }, { status: 400 });

    // papers of this exam (for full-marks bounds)
    const schedules = await prisma.cuExamSchedule.findMany({
      where: { examId: exam.id },
      select: { id: true, fullMarks: true },
    });
    const fullBySchedule = new Map(schedules.map((s) => [s.id, s.fullMarks]));

    const ops = [];
    for (const r of rows) {
      const scheduleId = Number(r.scheduleId);
      const studentId = Number(r.studentId);
      const full = fullBySchedule.get(scheduleId);
      if (!Number.isFinite(scheduleId) || !Number.isFinite(studentId) || full === undefined) continue;

      const absent = !!r.absent;
      let marks: number | null = null;
      if (!absent && r.marks !== undefined && r.marks !== null && r.marks !== "") {
        const n = Number(r.marks);
        if (!Number.isFinite(n) || n < 0) continue;
        if (n > full) continue; // out of bounds row — skip
        marks = Math.round(n * 100) / 100;
      }
      const note = r.note === undefined || r.note === null ? null : String(r.note).trim() || null;

      ops.push(
        prisma.cuExamMark.upsert({
          where: { scheduleId_studentId: { scheduleId, studentId } },
          create: { institutionId: session.institutionId!, scheduleId, studentId, marks, absent, note },
          update: { marks, absent, note },
        })
      );
    }
    if (ops.length === 0) return NextResponse.json({ error: "No valid rows" }, { status: 400 });

    await prisma.$transaction(ops);
    return NextResponse.json({ saved: ops.length });
  } catch {
    return NextResponse.json({ error: "Could not save marks" }, { status: 500 });
  }
}
