import { NextResponse } from "next/server";
import Papa from "papaparse";
import { prisma } from "@/lib/prisma";
import { requireRole } from "@/lib/auth";
import { ingestPunches, type PunchInput } from "@/lib/device-punch";

/* CSV import — the works-with-every-brand edge: import the attendance log
   export from the machine's desktop software (ZKTeco Attendance
   Management, BioSync, …). POST { machineId?, csvText, mapping?, dryRun? }.
   Without a mapping (dryRun) it returns detected columns + a preview. */

interface Mapping {
  idColumn: string;
  dateColumn: string;
  timeColumn?: string; // optional when dateColumn already holds date+time
  dateOrder?: "DMY" | "MDY" | "YMD"; // for slash/dash separated dates
}

/** tolerant date/time parser for machine exports */
function parsePunchDateTime(dateStr: string, timeStr: string | undefined, order: Mapping["dateOrder"]): Date | null {
  const combined = timeStr ? `${dateStr.trim()} ${timeStr.trim()}` : dateStr.trim();
  // pure ISO / Date-parseable ("2026-09-30 08:45", "2026-09-30T08:45:00")
  const iso = combined.replace(" ", "T");
  if (/^\d{4}-\d{2}-\d{2}T/.test(iso)) {
    const d = new Date(iso);
    if (!isNaN(d.getTime())) return d;
  }
  // split parts: date + optional 12h/24h time
  const m = combined.match(/^(\d{1,4})[\/\-.](\d{1,2})[\/\-.](\d{2,4})(?:[ ,]+(\d{1,2}):(\d{2})(?::(\d{2}))?\s*(am|pm)?)?/i);
  if (!m) {
    const d = new Date(combined);
    return isNaN(d.getTime()) ? null : d;
  }
  let [, a, b, c, hh, mm, ss, ampm] = m;
  let year: number, month: number, day: number;
  if (a.length === 4 || order === "YMD") {
    year = +a; month = +b; day = +c;
  } else if (order === "MDY") {
    month = +a; day = +b; year = +c;
  } else {
    day = +a; month = +b; year = +c; // DMY default
  }
  if (year < 100) year += 2000;
  let hour = hh ? +hh : 0;
  if (ampm) {
    const pm = ampm.toLowerCase() === "pm";
    if (pm && hour < 12) hour += 12;
    if (!pm && hour === 12) hour = 0;
  }
  const d = new Date(year, month - 1, day, hour, mm ? +mm : 0, ss ? +ss : 0);
  return isNaN(d.getTime()) ? null : d;
}

function detectMapping(columns: string[], rows: Record<string, string>[]): Mapping | null {
  const find = (...cands: string[]) =>
    columns.find((c) => cands.some((x) => c.toLowerCase().replace(/[^a-z]/g, "") === x));
  const idColumn = find("userid", "userno", "id", "badgenumber", "badgeno", "cardno", "cardnumber", "empcode", "employeecode", "pin");
  const dateColumn =
    find("datetime", "punchtime", "recordtime", "punchdate", "date", "attendancedate", "checkintime") ??
    find("date");
  const timeColumn = find("time", "punchtime2");
  if (!idColumn || !dateColumn) return null;
  const sample = rows[0]?.[dateColumn] ?? "";
  const dateOrder: Mapping["dateOrder"] =
    /^(\d{1,2})[\/\-.]/.test(sample) ? "DMY" : undefined;
  return { idColumn, dateColumn, timeColumn: timeColumn !== dateColumn ? timeColumn : undefined, dateOrder };
}

export async function POST(req: Request) {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session?.institutionId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  const body = (await req.json().catch(() => ({}))) as {
    machineId?: number;
    csvText?: string;
    mapping?: Mapping;
    dryRun?: boolean;
  };
  const csvText = String(body.csvText ?? "");
  if (!csvText.trim()) return NextResponse.json({ error: "CSV_REQUIRED" }, { status: 400 });

  const parsed = Papa.parse<Record<string, string>>(csvText.trim(), {
    header: true,
    skipEmptyLines: true,
    transformHeader: (h) => h.trim(),
  });
  const rows = (parsed.data ?? []).filter((r) => Object.values(r).some((v) => String(v ?? "").trim()));
  const columns = parsed.meta?.fields ?? [];

  if (body.dryRun || !body.mapping) {
    return NextResponse.json({
      columns,
      rows: rows.length,
      detected: detectMapping(columns, rows),
      preview: rows.slice(0, 5),
    });
  }

  const mapping = body.mapping;
  let machineId: number | null = null;
  if (body.machineId) {
    const machine = await prisma.attendanceMachine.findFirst({
      where: { id: Number(body.machineId), institutionId: session.institutionId },
      select: { id: true },
    });
    machineId = machine?.id ?? null;
  }

  const punches: PunchInput[] = [];
  for (const r of rows) {
    const identifier = String(r[mapping.idColumn] ?? "").trim();
    if (!identifier) continue;
    const punchedAt = parsePunchDateTime(
      String(r[mapping.dateColumn] ?? ""),
      mapping.timeColumn ? String(r[mapping.timeColumn] ?? "") : undefined,
      mapping.dateOrder
    );
    if (!punchedAt) continue;
    punches.push({
      institutionId: session.institutionId,
      machineId,
      source: "CSV",
      identifier,
      punchedAt,
      raw: { row: r },
    });
  }
  if (punches.length === 0) {
    return NextResponse.json({ error: "NO_VALID_ROWS" }, { status: 400 });
  }

  const result = await ingestPunches(punches);
  return NextResponse.json({ ok: true, ...result, skippedRows: rows.length - punches.length });
}
