import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireAnyRole } from "@/lib/auth";
import { STAFF_SIDE_ROLES } from "@/lib/auth";
import { saveMarkRows, teacherPaperIds, validateMarkRows } from "@/lib/marks";

/* Teacher marks entry: a staff login may enter marks ONLY for the papers it
    teaches — exam class + subject pairs matching their class-routine slots —
    and only while the exam's result is unpublished (same gate as admins).
    GET returns the teacher's exams/papers with the class roster + saved
    marks; POST validates rows against the same paper rules. */

async function resolveStaffId(userId: number): Promise<number | null> {
  const me = await prisma.user.findUnique({ where: { id: userId }, select: { staffId: true } });
  return me?.staffId ?? null;
}

export async function GET() {
  const session = await requireAnyRole([...STAFF_SIDE_ROLES]);
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const staffId = await resolveStaffId(session.userId);
  if (!staffId) {
    return NextResponse.json({ exams: [] });
  }

  const myPaperIds = await teacherPaperIds(staffId, session.institutionId);
  if (myPaperIds.size === 0) {
    return NextResponse.json({ exams: [] });
  }

  const papers = await prisma.examSchedule.findMany({
    where: { id: { in: [...myPaperIds] } },
    orderBy: [{ date: "asc" }],
    select: {
      id: true,
      date: true,
      startTime: true,
      fullMarks: true,
      cqMarks: true,
      mcqMarks: true,
      appliedMarks: true,
      subjectItem: { select: { name: true, nameBn: true } },
      exam: {
        select: {
          id: true,
          name: true,
          nameBn: true,
          status: true,
          startDate: true,
          endDate: true,
          resultPublished: true,
          classItemId: true,
          classItem: { select: { name: true, nameBn: true } },
          sectionItemId: true,
          sectionItem: { select: { name: true, nameBn: true } },
        },
      },
      marks: {
        select: {
          studentId: true,
          cq: true,
          mcq: true,
          applied: true,
          total: true,
          note: true,
        },
      },
    },
  });

  // group papers by exam + attach the roster of each exam's class
  const byExam = new Map<
    number,
    {
      id: number;
      name: string;
      nameBn: string | null;
      className: string | null;
      classNameBn: string | null;
      sectionName: string | null;
      sectionNameBn: string | null;
      status: string;
      startDate: Date;
      endDate: Date | null;
      resultPublished: boolean;
      papers: typeof papers;
    }
  >();

  for (const p of papers) {
    const e = p.exam;
    let entry = byExam.get(e.id);
    if (!entry) {
      entry = {
        id: e.id,
        name: e.name,
        nameBn: e.nameBn,
        className: e.classItem?.name ?? null,
        classNameBn: e.classItem?.nameBn ?? null,
        sectionName: e.sectionItem?.name ?? null,
        sectionNameBn: e.sectionItem?.nameBn ?? null,
        status: e.status,
        startDate: e.startDate,
        endDate: e.endDate,
        resultPublished: e.resultPublished,
        papers: [],
      };
      byExam.set(e.id, entry);
    }
    entry.papers.push(p);
  }

  // roster: active students of each exam's class (shared across its papers)
  const classIds = [...new Set(papers.map((p) => p.exam.classItemId).filter((x): x is number => x != null))];
  const students = classIds.length
    ? await prisma.student.findMany({
        where: { institutionId: session.institutionId, status: "ACTIVE", classItemId: { in: classIds } },
        orderBy: { roll: "asc" },
        select: { id: true, roll: true, name: true, nameBn: true, classItemId: true, sectionItemId: true },
      })
    : [];

  return NextResponse.json({
    exams: [...byExam.values()].sort((a, b) => (a.startDate < b.startDate ? 1 : -1)),
    roster: students,
  });
}

export async function POST(req: Request) {
  const session = await requireAnyRole([...STAFF_SIDE_ROLES]);
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  try {
    const staffId = await resolveStaffId(session.userId);
    if (!staffId) {
      return NextResponse.json({ error: "FORBIDDEN" }, { status: 403 });
    }

    const body = await req.json();
    const examId = Number(body.examId);
    if (!examId) return NextResponse.json({ error: "EXAM_REQUIRED" }, { status: 400 });

    const exam = await prisma.exam.findFirst({
      where: {
        id: examId,
        OR: [{ institutionId: session.institutionId }, { branchId: session.institutionId }],
      },
      select: { id: true, institutionId: true, resultPublished: true },
    });
    if (!exam) return NextResponse.json({ error: "Not found" }, { status: 404 });
    if (exam.resultPublished) {
      return NextResponse.json({ error: "RESULT_PUBLISHED" }, { status: 409 });
    }

    const myPaperIds = await teacherPaperIds(staffId, session.institutionId);
    if (myPaperIds.size === 0) {
      return NextResponse.json({ error: "FORBIDDEN" }, { status: 403 });
    }

    const raw: unknown[] = Array.isArray(body.rows) ? body.rows : [];
    const result = await validateMarkRows(raw, exam.id, exam.institutionId, myPaperIds);
    if (!result.ok) {
      const status = result.error === "NOT_YOUR_PAPER" ? 403 : 400;
      return NextResponse.json({ error: result.error }, { status });
    }

    const saved = await saveMarkRows(result.rows, exam.institutionId);
    return NextResponse.json({ saved });
  } catch {
    return NextResponse.json({ error: "Could not save marks" }, { status: 500 });
  }
}
