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

/* OMR Sheet Management — institution side.
   GET    (no params)               exams + papers with key/response stats
   GET    ?examScheduleId=…         one paper: keys + responses + summary
   PUT                             create/update an answer key (paper + set)
   DELETE ?id=…                    remove a key (cascades its responses)
   POST   {action:"import"}        scanned rows {roll,set,responses} → computed
   POST   {action:"push"}          matched rows → exam MCQ marks */

const SET_RE = /^[A-Z]$/;

function normalizeBubbles(raw: string, questions: number): { bubbles: string; error?: string } {
  const b = raw.trim().toUpperCase().replace(/[^A-D-]/g, "");
  if (b.length !== questions) return { bubbles: b, error: `LENGTH:${b.length}/${questions}` };
  return { bubbles: b };
}

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 };
}

export async function GET(req: Request) {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const url = new URL(req.url);
  const scheduleIdParam = url.searchParams.get("examScheduleId");

  if (scheduleIdParam) {
    /* ---- one paper: keys + responses ---- */
    const scheduleId = Number(scheduleIdParam);
    const schedule = await prisma.examSchedule.findFirst({
      where: { id: scheduleId, exam: { institutionId: session.institutionId } },
      select: {
        id: true,
        mcqMarks: true,
        cqMarks: true,
        fullMarks: true,
        subjectItem: { select: { name: true, nameBn: true } },
        exam: { select: { id: true, name: true, resultPublished: true } },
      },
    });
    if (!schedule) return NextResponse.json({ error: "Not found" }, { status: 404 });

    const sheets = await prisma.omrSheet.findMany({
      where: { examScheduleId: scheduleId },
      include: { responses: { orderBy: { rawRoll: "asc" } } },
      orderBy: { setLabel: "asc" },
    });
    const students = await prisma.student.findMany({
      where: { institutionId: session.institutionId },
      select: { id: true, roll: true, name: true },
    });
    const byId = new Map(students.map((s) => [s.id, s]));

    return NextResponse.json({
      schedule,
      sheets: sheets.map((sh) => ({
        id: sh.id,
        setLabel: sh.setLabel,
        questions: sh.questions,
        markPerQuestion: Number(sh.markPerQuestion),
        negativeMark: Number(sh.negativeMark),
        answerKey: sh.answerKey,
        responses: sh.responses.map((r) => ({
          id: r.id,
          rawRoll: r.rawRoll,
          studentId: r.studentId,
          studentName: r.studentId ? byId.get(r.studentId)?.name ?? null : null,
          responses: r.responses,
          correct: r.correct,
          wrong: r.wrong,
          blank: r.blank,
          mcqMarks: Number(r.mcqMarks),
          status: r.status,
          statusNote: r.statusNote,
        })),
      })),
    });
  }

  /* ---- overview: exams + papers + stats ---- */
  const exams = await prisma.exam.findMany({
    where: { institutionId: session.institutionId },
    orderBy: { id: "desc" },
    select: {
      id: true,
      name: true,
      nameBn: true,
      status: true,
      resultPublished: true,
      schedules: {
        orderBy: { id: "asc" },
        select: {
          id: true,
          fullMarks: true,
          mcqMarks: true,
          cqMarks: true,
          date: true,
          subjectItem: { select: { name: true, nameBn: true } },
        },
      },
    },
  });

  // per-schedule key + response totals
  const sheets = await prisma.omrSheet.findMany({
    where: { institutionId: session.institutionId },
    select: {
      id: true,
      examScheduleId: true,
      _count: { select: { responses: { where: { status: "MATCHED" } } } },
      responses: { select: { id: true } },
    },
  });
  const stats = new Map<number, { keys: number; responses: number; matched: number }>();
  for (const sh of sheets) {
    const cur = stats.get(sh.examScheduleId) ?? { keys: 0, responses: 0, matched: 0 };
    cur.keys += 1;
    cur.responses += sh.responses.length;
    cur.matched += sh._count.responses;
    stats.set(sh.examScheduleId, cur);
  }

  return NextResponse.json({
    exams: exams.map((e) => ({
      id: e.id,
      name: e.name,
      nameBn: e.nameBn,
      status: e.status,
      resultPublished: e.resultPublished,
      papers: e.schedules.map((p) => ({
        id: p.id,
        subject: p.subjectItem?.name ?? "—",
        subjectBn: p.subjectItem?.nameBn ?? null,
        date: p.date,
        fullMarks: p.fullMarks,
        mcqMarks: p.mcqMarks,
        cqMarks: p.cqMarks,
        ...(stats.get(p.id) ?? { keys: 0, responses: 0, matched: 0 }),
      })),
    })),
  });
}

/** Create or update the answer key of one paper + set. */
export async function PUT(req: Request) {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  try {
    const body = await req.json();
    const examScheduleId = Number(body.examScheduleId);
    const setLabel = body.setLabel ? String(body.setLabel).trim().toUpperCase() : null;
    const questions = Number(body.questions);
    const markPerQuestion = Number(body.markPerQuestion ?? 1);
    const negativeMark = Number(body.negativeMark ?? 0);
    const answerKey = String(body.answerKey ?? "").trim().toUpperCase().replace(/[^A-D-]/g, "");

    if (!examScheduleId || !Number.isInteger(questions) || questions < 1 || questions > 300) {
      return NextResponse.json({ error: "INVALID_SETUP" }, { status: 400 });
    }
    if (setLabel && !SET_RE.test(setLabel)) {
      return NextResponse.json({ error: "INVALID_SET" }, { status: 400 });
    }
    if (!(markPerQuestion > 0) || negativeMark < 0) {
      return NextResponse.json({ error: "INVALID_SETUP" }, { status: 400 });
    }
    if (answerKey.length !== questions) {
      return NextResponse.json({ error: "KEY_LENGTH" }, { status: 400 });
    }

    const schedule = await prisma.examSchedule.findFirst({
      where: { id: examScheduleId, exam: { institutionId: session.institutionId } },
      select: { id: true },
    });
    if (!schedule) return NextResponse.json({ error: "Not found" }, { status: 404 });

    const sheet = await prisma.omrSheet.upsert({
      where: { examScheduleId_setLabel: { examScheduleId, setLabel: setLabel ?? "" } },
      create: {
        institutionId: session.institutionId,
        examScheduleId,
        setLabel,
        questions,
        markPerQuestion,
        negativeMark,
        answerKey,
        createdById: session.userId,
      },
      update: { questions, markPerQuestion, negativeMark, answerKey },
    });
    return NextResponse.json({ sheet: { id: sheet.id } });
  } catch {
    return NextResponse.json({ error: "Could not save key" }, { status: 500 });
  }
}

/** Remove a key with its imported responses. */
export async function DELETE(req: Request) {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const id = Number(new URL(req.url).searchParams.get("id"));
  const sheet = await prisma.omrSheet.findFirst({
    where: { id, institutionId: session.institutionId },
    select: { id: true },
  });
  if (!sheet) return NextResponse.json({ error: "Not found" }, { status: 404 });
  await prisma.omrSheet.delete({ where: { id: sheet.id } });
  return NextResponse.json({ success: true });
}

export async function POST(req: Request) {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session || !session.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  try {
    const body = await req.json();
    const action = String(body.action ?? "");
    const examScheduleId = Number(body.examScheduleId);
    const schedule = await prisma.examSchedule.findFirst({
      where: { id: examScheduleId, exam: { institutionId: session.institutionId } },
      select: {
        id: true,
        mcqMarks: true,
        mcqPassMarks: true,
        exam: { select: { resultPublished: true } },
      },
    });
    if (!schedule) return NextResponse.json({ error: "Not found" }, { status: 404 });
    if (schedule.exam.resultPublished) {
      return NextResponse.json({ error: "RESULT_PUBLISHED" }, { status: 409 });
    }

    /* ---- import scanned rows ---- */
    if (action === "import") {
      const rows: { roll?: string; set?: string; responses?: string }[] = Array.isArray(body.rows) ? body.rows : [];
      if (!rows.length) return NextResponse.json({ error: "NO_ROWS" }, { status: 400 });

      const sheets = await prisma.omrSheet.findMany({ where: { examScheduleId } });
      const bySet = new Map(sheets.map((s) => [s.setLabel ?? "", s]));
      const students = await prisma.student.findMany({
        where: { institutionId: session.institutionId, status: "ACTIVE" },
        select: { id: true, roll: true },
      });
      const byRoll = new Map(students.map((s) => [s.roll.trim().toUpperCase(), s.id]));

      const parsed: {
        rawRoll: string;
        setLabel: string | null;
        sheetId: number | null;
        studentId: number | null;
        bubbles: string;
        correct: number;
        wrong: number;
        blank: number;
        mcqMarks: number;
        status: string;
        statusNote: string | null;
      }[] = [];

      for (const row of rows) {
        const rawRoll = String(row.roll ?? "").trim();
        const setLabel = row.set ? String(row.set).trim().toUpperCase() : null;
        const base = {
          rawRoll,
          setLabel,
          sheetId: null as number | null,
          studentId: byRoll.get(rawRoll.toUpperCase()) ?? null,
          bubbles: "",
          correct: 0,
          wrong: 0,
          blank: 0,
          mcqMarks: 0,
          status: "INVALID",
          statusNote: null as string | null,
        };

        if (!rawRoll) {
          parsed.push({ ...base, statusNote: "EMPTY_ROLL" });
          continue;
        }
        const sheet = setLabel
          ? bySet.get(setLabel)
          : sheets.length === 1
            ? sheets[0]
            : undefined;
        if (!sheet) {
          parsed.push({ ...base, statusNote: setLabel ? `UNKNOWN_SET:${setLabel}` : "NO_KEY" });
          continue;
        }
        const { bubbles, error } = normalizeBubbles(String(row.responses ?? ""), sheet.questions);
        if (error) {
          parsed.push({ ...base, bubbles, sheetId: sheet.id, statusNote: error });
          continue;
        }
        const sc = score(bubbles, sheet.answerKey, Number(sheet.markPerQuestion), Number(sheet.negativeMark));
        parsed.push({
          ...base,
          bubbles,
          sheetId: sheet.id,
          ...sc,
          status: base.studentId ? "MATCHED" : "UNMATCHED",
          statusNote: base.studentId ? null : "UNKNOWN_ROLL",
        });
      }

      // upsert per sheet + roll (latest scan wins)
      let saved = 0;
      for (const p of parsed) {
        if (!p.sheetId) continue;
        await prisma.omrResponse.upsert({
          where: { omrSheetId_rawRoll: { omrSheetId: p.sheetId, rawRoll: p.rawRoll } },
          create: {
            omrSheetId: p.sheetId,
            studentId: p.studentId,
            rawRoll: p.rawRoll,
            responses: p.bubbles,
            correct: p.correct,
            wrong: p.wrong,
            blank: p.blank,
            mcqMarks: p.mcqMarks,
            status: p.status,
            statusNote: p.statusNote,
          },
          update: {
            studentId: p.studentId,
            responses: p.bubbles,
            correct: p.correct,
            wrong: p.wrong,
            blank: p.blank,
            mcqMarks: p.mcqMarks,
            status: p.status,
            statusNote: p.statusNote,
          },
        });
        saved++;
      }

      return NextResponse.json({
        saved,
        matched: parsed.filter((p) => p.status === "MATCHED").length,
        unmatched: parsed.filter((p) => p.status === "UNMATCHED").length,
        invalid: parsed.filter((p) => p.status === "INVALID").length,
        preview: parsed.map((p) => ({
          roll: p.rawRoll,
          set: p.setLabel,
          status: p.status,
          note: p.statusNote,
          correct: p.correct,
          wrong: p.wrong,
          blank: p.blank,
          marks: p.mcqMarks,
        })),
      });
    }

    /* ---- push matched responses into exam MCQ marks ---- */
    if (action === "push") {
      const sheets = await prisma.omrSheet.findMany({
        where: { examScheduleId },
        include: { responses: { where: { status: "MATCHED" } } },
      });
      // one row per student: multiple sets → highest marks win
      const best = new Map<number, number>();
      for (const sh of sheets) {
        for (const r of sh.responses) {
          if (r.studentId === null) continue;
          const cur = best.get(r.studentId);
          const m = Number(r.mcqMarks);
          if (cur === undefined || m > cur) best.set(r.studentId, m);
        }
      }
      if (!best.size) return NextResponse.json({ error: "NOTHING_TO_PUSH" }, { status: 400 });

      let updated = 0;
      for (const [studentId, mcq] of best) {
        // never write past the paper's MCQ full marks
        const value = schedule.mcqMarks ? Math.min(mcq, schedule.mcqMarks) : mcq;
        await prisma.examMark.upsert({
          where: { examScheduleId_studentId: { examScheduleId, studentId } },
          create: { examScheduleId, studentId, institutionId: session.institutionId!, mcq: Math.round(value) },
          update: { mcq: Math.round(value) },
        });
        updated++;
      }
      return NextResponse.json({ updated });
    }

    return NextResponse.json({ error: "Invalid action" }, { status: 400 });
  } catch {
    return NextResponse.json({ error: "Could not process" }, { status: 500 });
  }
}
