/* Supplemental OMR demo seeder — answer keys + scanned-response rows so the
   portal OMR Sheet Management page carries realistic demo data.
   Idempotent: keys are skipped when they already exist, responses upsert.
   Run: npx tsx prisma/seed-omr.ts */
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

const INSTITUTION_ID = 42; // Demo High School (HS-1001)

function score(bubbles: string, key: string, markPer: number, negative: number) {
  let correct = 0;
  let wrong = 0;
  let blank = 0;
  for (let i = 0; i < key.length; i++) {
    const b = bubbles[i] ?? "-";
    if (b === "-" || key[i] === "-") blank++;
    else if (b === key[i]) correct++;
    else wrong++;
  }
  const mcqMarks = Math.max(0, Math.round((correct * markPer - wrong * negative) * 100) / 100);
  return { correct, wrong, blank, mcqMarks };
}

/** mutate a perfect answer string into a realistic attempt */
function attempt(key: string, wrongIdx: number[], blankIdx: number[] = []) {
  const opts = ["A", "B", "C", "D"];
  const chars = key.split("");
  for (const i of wrongIdx) {
    if (chars[i] && chars[i] !== "-") {
      chars[i] = opts.find((o) => o !== chars[i] && o !== "-") ?? "A";
    }
  }
  for (const i of blankIdx) chars[i] = "-";
  return chars.join("");
}

interface SeedSheet {
  examScheduleId: number;
  setLabel: string | null;
  questions: number;
  markPerQuestion: number;
  negativeMark: number;
  answerKey: string;
}

interface SeedRow {
  roll: string;
  bubbles: string; // full-length attempt; short string → INVALID demo
}

interface SeedPaper {
  sheet: SeedSheet;
  rows: SeedRow[];
}

function keyString(seed: number, questions: number) {
  // deterministic pseudo-random key so re-seeding is stable
  let x = seed;
  const opts = ["A", "B", "C", "D"];
  return Array.from({ length: questions }, () => {
    x = (x * 1103515245 + 12345) % 2147483648;
    return opts[x % 4];
  }).join("");
}

const papers: SeedPaper[] = [
  // ---- First Terminal · Mathematics (25q × 1, sets A & B) ----
  {
    sheet: { examScheduleId: 196, setLabel: "A", questions: 25, markPerQuestion: 1, negativeMark: 0, answerKey: keyString(11, 25) },
    rows: [
      { roll: "1001", bubbles: "" }, // perfect — filled below
      { roll: "1002", bubbles: "" },
      { roll: "9999", bubbles: "" }, // unknown roll demo
    ],
  },
  {
    sheet: { examScheduleId: 196, setLabel: "B", questions: 25, markPerQuestion: 1, negativeMark: 0, answerKey: keyString(22, 25) },
    rows: [
      { roll: "2001", bubbles: "" },
      { roll: "1003", bubbles: "SHORT" }, // invalid-length demo
    ],
  },
  // ---- First Terminal · English (30q × 1, negative 0.25, sets A & B) ----
  {
    sheet: { examScheduleId: 197, setLabel: "A", questions: 30, markPerQuestion: 1, negativeMark: 0.25, answerKey: keyString(33, 30) },
    rows: [{ roll: "1001", bubbles: "" }, { roll: "1002", bubbles: "" }],
  },
  {
    sheet: { examScheduleId: 197, setLabel: "B", questions: 30, markPerQuestion: 1, negativeMark: 0.25, answerKey: keyString(44, 30) },
    rows: [{ roll: "2001", bubbles: "" }, { roll: "9998", bubbles: "" }],
  },
  // ---- Final Examination · Bangla (single set, 20q) ----
  {
    sheet: { examScheduleId: 200, setLabel: null, questions: 20, markPerQuestion: 1, negativeMark: 0, answerKey: keyString(55, 20) },
    rows: [{ roll: "1003", bubbles: "" }, { roll: "1004", bubbles: "" }, { roll: "9997", bubbles: "" }],
  },
  // ---- Annual Test · English (single set, 25q) ----
  {
    sheet: { examScheduleId: 203, setLabel: null, questions: 25, markPerQuestion: 1, negativeMark: 0, answerKey: keyString(66, 25) },
    rows: [{ roll: "1005", bubbles: "" }, { roll: "1006", bubbles: "" }],
  },
];

async function main() {
  const inst = await prisma.institution.findUnique({ where: { code: "HS-1001" } });
  if (!inst) throw new Error("HS-1001 not found — run the main seeder first");

  const students = await prisma.student.findMany({
    where: { institutionId: INSTITUTION_ID, status: "ACTIVE" },
    select: { id: true, roll: true },
  });
  const byRoll = new Map(students.map((s) => [s.roll, s.id]));

  let keys = 0;
  let rows = 0;
  let matched = 0;
  let unmatched = 0;
  let invalid = 0;

  for (const paper of papers) {
    const { sheet } = paper;
    const omrSheet = await prisma.omrSheet.upsert({
      where: {
        examScheduleId_setLabel: { examScheduleId: sheet.examScheduleId, setLabel: sheet.setLabel ?? "" },
      },
      create: {
        institutionId: INSTITUTION_ID,
        examScheduleId: sheet.examScheduleId,
        setLabel: sheet.setLabel,
        questions: sheet.questions,
        markPerQuestion: sheet.markPerQuestion,
        negativeMark: sheet.negativeMark,
        answerKey: sheet.answerKey,
      },
      update: {
        questions: sheet.questions,
        markPerQuestion: sheet.markPerQuestion,
        negativeMark: sheet.negativeMark,
        answerKey: sheet.answerKey,
      },
    });
    keys++;

    for (const [i, row] of paper.rows.entries()) {
      // realistic attempts per position: first row strong, second average,
      // third weak; unknown rolls get an average attempt; "SHORT" stays invalid
      const bubbles =
        row.bubbles === ""
          ? row.roll.startsWith("999")
            ? attempt(sheet.answerKey, [3, 7, 11], [15])
            : i === 0
              ? attempt(sheet.answerKey, [24 % sheet.questions], [])
              : attempt(sheet.answerKey, [2, 6, 10, 14], [18 % sheet.questions])
          : row.bubbles;
      const badLength = bubbles.length !== sheet.questions;
      const sc = badLength
        ? { correct: 0, wrong: 0, blank: 0, mcqMarks: 0 }
        : score(bubbles, sheet.answerKey, sheet.markPerQuestion, sheet.negativeMark);
      const studentId = byRoll.get(row.roll) ?? null;
      const status = badLength ? "INVALID" : studentId ? "MATCHED" : "UNMATCHED";
      if (status === "MATCHED") matched++;
      else if (status === "UNMATCHED") unmatched++;
      else invalid++;

      await prisma.omrResponse.upsert({
        where: { omrSheetId_rawRoll: { omrSheetId: omrSheet.id, rawRoll: row.roll } },
        create: {
          omrSheetId: omrSheet.id,
          studentId,
          rawRoll: row.roll,
          responses: bubbles,
          ...sc,
          status,
          statusNote: badLength ? `LENGTH:${bubbles.length}/${sheet.questions}` : studentId ? null : "UNKNOWN_ROLL",
        },
        update: {
          studentId,
          responses: bubbles,
          ...sc,
          status,
          statusNote: badLength ? `LENGTH:${bubbles.length}/${sheet.questions}` : studentId ? null : "UNKNOWN_ROLL",
        },
      });
      rows++;
    }
  }

  console.log(
    `OMR demo data: ${keys} answer keys, ${rows} responses (${matched} matched, ${unmatched} unknown roll, ${invalid} invalid)`
  );
}

main()
  .catch((e) => {
    console.error(e);
    process.exitCode = 1;
  })
  .finally(() => prisma.$disconnect());
