import { prisma } from "@/lib/prisma";

/* Shared marks-entry validation for exam papers. Used by the admin marks
   route (every paper of the exam) and the member /api/me/marks route (only
   the papers the logged-in teacher teaches). Validates each row against the
   paper's marks split — obtained marks can never exceed full/part marks —
   and upserts per (examScheduleId, studentId). */

export interface MarkRowInput {
  examScheduleId: number;
  studentId: number;
  cq: number | null;
  mcq: number | null;
  applied: number | null;
  total: number | null;
  note: string | null;
}

export type MarkRowResult =
  | { ok: true; rows: MarkRowInput[] }
  | { ok: false; error: "INVALID_ROW" | "NOT_YOUR_PAPER" | "MARKS_EXCEED" };

function coerce(v: unknown): number | null {
  if (v === undefined || v === null || v === "") return null;
  const n = Number(v);
  return Number.isFinite(n) && n >= 0 ? Math.round(n) : null;
}

/** Clean + validate raw rows for one exam. When `allowedPaperIds` is given,
    rows outside it are rejected (teacher scope) instead of silently dropped. */
export async function validateMarkRows(
  raw: unknown[],
  examId: number,
  institutionId: number,
  allowedPaperIds?: Set<number>
): Promise<MarkRowResult> {
  const papers = await prisma.examSchedule.findMany({
    where: { examId },
    select: { id: true, fullMarks: true, cqMarks: true, mcqMarks: true, appliedMarks: true },
  });
  const paperById = new Map(papers.map((p) => [p.id, p]));

  const eligible = new Set(
    (
      await prisma.student.findMany({
        where: { institutionId, status: "ACTIVE" },
        select: { id: true },
      })
    ).map((s) => s.id)
  );

  const cleaned: MarkRowInput[] = [];
  for (const item of raw) {
    const r = (item ?? {}) as Record<string, unknown>;
    const examScheduleId = Number(r.examScheduleId);
    const studentId = Number(r.studentId);
    const paper = paperById.get(examScheduleId);
    if (!paper || !eligible.has(studentId)) {
      return { ok: false, error: "INVALID_ROW" };
    }
    if (allowedPaperIds && !allowedPaperIds.has(examScheduleId)) {
      return { ok: false, error: "NOT_YOUR_PAPER" };
    }
    const cq = coerce(r.cq);
    const mcq = coerce(r.mcq);
    const applied = coerce(r.applied);
    const total = coerce(r.total);
    const note = String(r.note ?? "").trim() || null;
    if (cq === null && mcq === null && applied === null && total === null && !note) continue;
    const exceeds =
      (total !== null && total > paper.fullMarks) ||
      (cq !== null && paper.cqMarks !== null && cq > paper.cqMarks) ||
      (mcq !== null && paper.mcqMarks !== null && mcq > paper.mcqMarks) ||
      (applied !== null && paper.appliedMarks !== null && applied > paper.appliedMarks);
    if (exceeds) {
      return { ok: false, error: "MARKS_EXCEED" };
    }
    cleaned.push({ examScheduleId, studentId, cq, mcq, applied, total, note });
  }
  return { ok: true, rows: cleaned };
}

/** Upsert validated rows in one transaction; returns the saved count. */
export async function saveMarkRows(rows: MarkRowInput[], institutionId: number): Promise<number> {
  await prisma.$transaction(
    rows.map((row) =>
      prisma.examMark.upsert({
        where: { examScheduleId_studentId: { examScheduleId: row.examScheduleId, studentId: row.studentId } },
        create: { institutionId, ...row },
        update: { cq: row.cq, mcq: row.mcq, applied: row.applied, total: row.total, note: row.note },
      })
    )
  );
  return rows.length;
}

/** The exam papers a teacher owns: exam class + subject pairs that match one
    of their class-routine slots (ClassSchedule rows). */
export async function teacherPaperIds(staffId: number, institutionId: number): Promise<Set<number>> {
  const slots = await prisma.classSchedule.findMany({
    where: { institutionId, staffId, isActive: true },
    select: { classItemId: true, subjectItemId: true },
  });
  if (slots.length === 0) return new Set();

  const pairKeys = new Set(
    slots
      .filter((s) => s.classItemId != null && s.subjectItemId != null)
      .map((s) => `${s.classItemId}:${s.subjectItemId}`)
  );

  const papers = await prisma.examSchedule.findMany({
    where: {
      exam: { institutionId, OR: [{ branchId: null }, { branchId: institutionId }] },
    },
    select: {
      id: true,
      subjectItemId: true,
      exam: { select: { classItemId: true } },
    },
  });
  return new Set(
    papers
      .filter((p) => pairKeys.has(`${p.exam.classItemId}:${p.subjectItemId}`))
      .map((p) => p.id)
  );
}
