import crypto from "crypto";
import { prisma } from "@/lib/prisma";

/* Short verification codes printed as QR text on every document.
   Stored WITHOUT hyphens (EMS7K2QX9MD), displayed/typed WITH them
   (EMS-7K2Q-X9MD) — lookup normalizes both, so any typing style works. */

// no ambiguous characters (0/O, 1/I) so codes read reliably from paper
const ALPHABET = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";

export function generateCode(): string {
  const bytes = crypto.randomBytes(8);
  let body = "";
  for (let i = 0; i < 8; i++) body += ALPHABET[bytes[i] % ALPHABET.length];
  return `EMS${body}`;
}

/** EMS7K2QX9MD → EMS-7K2Q-X9MD (for display and QR text) */
export function formatCode(stored: string): string {
  const s = stored.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
  return `${s.slice(0, 3)}-${s.slice(3, 7)}-${s.slice(7, 11)}`;
}

/** any user typing ("ems 7k2q x9md", "EMS-7K2Q-X9MD") → stored form */
export function normalizeCode(input: string): string {
  return input.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
}

export type VerifyKind = "RESULT" | "EXAM_SCHEDULE" | "INVENTORY" | "ADMIT_CARD";

/**
 * Idempotent mint: one stable code per (kind, institution, refKey, student).
 * Returns the DISPLAY form (EMS-XXXX-XXXX) ready for the QR image.
 */
export async function ensureCode(opts: {
  kind: VerifyKind;
  institutionId: number;
  refKey: string;
  studentId?: number;
  label?: string | null;
  total?: string | null;
  grade?: string | null;
}): Promise<string> {
  const studentId = opts.studentId ?? 0;
  const existing = await prisma.verificationCode.findUnique({
    where: {
      kind_institutionId_refKey_studentId: {
        kind: opts.kind,
        institutionId: opts.institutionId,
        refKey: opts.refKey,
        studentId,
      },
    },
  });
  if (existing) return existing.code;

  for (let attempt = 0; attempt < 3; attempt++) {
    try {
      const row = await prisma.verificationCode.create({
        data: {
          code: generateCode(),
          kind: opts.kind,
          institutionId: opts.institutionId,
          refKey: opts.refKey,
          studentId,
          label: opts.label ?? null,
          total: opts.total ?? null,
          grade: opts.grade ?? null,
        },
      });
      return row.code;
    } catch (err) {
      // P2002 = unique violation: either a random-code collision (retry) or a
      // concurrent mint of the same reference (picked up by the find below)
      if ((err as { code?: string }).code !== "P2002") throw err;
    }
  }
  const raced = await prisma.verificationCode.findUnique({
    where: {
      kind_institutionId_refKey_studentId: {
        kind: opts.kind,
        institutionId: opts.institutionId,
        refKey: opts.refKey,
        studentId,
      },
    },
  });
  if (raced) return raced.code;
  throw new Error("CODE_MINT_FAILED");
}

/** Batch mint for result sheets — one code per student, two queries when
    everything exists already. Returns studentId → STORED code. */
export async function ensureCodes(opts: {
  institutionId: number;
  refKey: string;
  label?: string | null;
  rows: { studentId: number; total?: string | number | null; grade?: string | null }[];
}): Promise<Map<number, string>> {
  const ids = opts.rows.map((r) => r.studentId).filter((id) => id > 0);
  const out = new Map<number, string>();
  if (ids.length === 0) return out;

  const existing = await prisma.verificationCode.findMany({
    where: {
      kind: "RESULT",
      institutionId: opts.institutionId,
      refKey: opts.refKey,
      studentId: { in: ids },
    },
    select: { studentId: true, code: true },
  });
  for (const row of existing) out.set(row.studentId, row.code);

  const missing = opts.rows.filter((r) => r.studentId > 0 && !out.has(r.studentId));
  if (missing.length > 0) {
    try {
      await prisma.verificationCode.createMany({
        data: missing.map((r) => ({
          code: generateCode(),
          kind: "RESULT",
          institutionId: opts.institutionId,
          refKey: opts.refKey,
          studentId: r.studentId,
          label: opts.label ?? null,
          total: r.total != null ? String(r.total) : null,
          grade: r.grade ?? null,
        })),
      });
    } catch {
      // random-code collision inside the batch — fall back row by row
    }
    const created = await prisma.verificationCode.findMany({
      where: {
        kind: "RESULT",
        institutionId: opts.institutionId,
        refKey: opts.refKey,
        studentId: { in: missing.map((r) => r.studentId) },
      },
      select: { studentId: true, code: true },
    });
    for (const row of created) out.set(row.studentId, row.code);
  }
  return out;
}
