import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { getMachineAuth } from "@/lib/machineAuthCache";
import { z } from "zod";

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return value as Record<string, unknown>;
}

function unwrapEnvelope(raw: unknown) {
  const record = asRecord(raw);
  if (!record) return raw;
  const payload = asRecord(record.payload);
  if (!payload) return raw;

  const hasMeta =
    record.schemaVersion !== undefined ||
    record.machineId !== undefined ||
    record.tsMs !== undefined ||
    record.tsDevice !== undefined ||
    record.seq !== undefined ||
    record.type !== undefined;
  if (!hasMeta) return raw;

  return {
    ...payload,
    machineId: record.machineId ?? payload.machineId,
    tsMs: record.tsMs ?? payload.tsMs,
    tsDevice: record.tsDevice ?? payload.tsDevice,
    schemaVersion: record.schemaVersion ?? payload.schemaVersion,
    seq: record.seq ?? payload.seq,
  };
}

function asNumber(value: unknown) {
  if (typeof value === "number" && Number.isFinite(value)) return value;
  if (typeof value === "string" && value.trim() !== "") {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : undefined;
  }
  return undefined;
}

function normalizeCycleInput(raw: unknown): Record<string, unknown> | null {
  const row = asRecord(raw);
  if (!row) return null;
  const data = asRecord(row.data);

  const fromRowOrData = (keys: string[]) => {
    for (const key of keys) {
      if (row[key] !== undefined) return row[key];
      if (data && data[key] !== undefined) return data[key];
    }
    return undefined;
  };

  return {
    ...row,
    actual_cycle_time: fromRowOrData(["actual_cycle_time", "actualCycleTime", "actual_cycle", "actual"]),
    theoretical_cycle_time: fromRowOrData([
      "theoretical_cycle_time",
      "theoreticalCycleTime",
      "cycleTime",
      "cycle_time",
      "ideal",
    ]),
    cycle_count: fromRowOrData(["cycle_count", "cycleCount"]),
    work_order_id: fromRowOrData(["work_order_id", "workOrderId"]),
    good_delta: fromRowOrData(["good_delta", "goodDelta"]),
    // `scrap_total` is cumulative and should not be persisted as per-cycle delta.
    scrap_delta: fromRowOrData(["scrap_delta", "scrapDelta"]),
    timestamp: fromRowOrData(["timestamp", "tsMs"]),
    ts: fromRowOrData(["ts", "tsMs"]),
    event_timestamp: fromRowOrData(["event_timestamp", "eventTimestamp"]),
  };
}

const numberFromAny = z.preprocess((value) => {
  if (typeof value === "number") return value;
  if (typeof value === "string" && value.trim() !== "") return Number(value);
  return value;
}, z.number().finite());

const intFromAny = z.preprocess((value) => {
  if (typeof value === "number") return Math.trunc(value);
  if (typeof value === "string" && value.trim() !== "") return Math.trunc(Number(value));
  return value;
}, z.number().int().finite());

const machineIdSchema = z.string().uuid();

const cycleSchema = z
  .object({
    actual_cycle_time: numberFromAny,
    theoretical_cycle_time: numberFromAny.optional(),
    cycle_count: intFromAny.optional(),
    work_order_id: z.string().trim().max(64).optional(),
    sku: z.string().trim().max(64).optional(),
    cavities: intFromAny.optional(),
    good_delta: intFromAny.optional(),
    scrap_delta: intFromAny.optional(),
    timestamp: numberFromAny.optional(),
    ts: numberFromAny.optional(),
    event_timestamp: numberFromAny.optional(),
  })
  .passthrough();

export async function POST(req: Request) {
  const apiKey = req.headers.get("x-api-key");
  if (!apiKey) return NextResponse.json({ ok: false, error: "Missing api key" }, { status: 401 });

  let body: unknown = await req.json().catch(() => null);
  body = unwrapEnvelope(body);
  const bodyRecord = asRecord(body) ?? {};

  const machineId =
    bodyRecord.machineId ??
    bodyRecord.machine_id ??
    (asRecord(bodyRecord.machine)?.id ?? null);
  if (!machineId || !machineIdSchema.safeParse(String(machineId)).success) {
    return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 400 });
  }

  const machine = await getMachineAuth(String(machineId), apiKey);
  if (!machine) return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 });

  const cyclesRaw = bodyRecord.cycles ?? bodyRecord.cycle;
  if (!cyclesRaw) {
    return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 400 });
  }

  const cycleList = (Array.isArray(cyclesRaw) ? cyclesRaw : [cyclesRaw])
    .map((row) => normalizeCycleInput(row))
    .filter((row): row is Record<string, unknown> => !!row);

  if (!cycleList.length) {
    return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 400 });
  }

  const parsedCycles = z.array(cycleSchema).safeParse(cycleList);
  if (!parsedCycles.success) {
    return NextResponse.json({ ok: false, error: "Invalid payload" }, { status: 400 });
  }

  const fallbackTsMs =
    asNumber(bodyRecord.tsMs) ||
    asNumber(bodyRecord.tsDevice) ||
    undefined;

  const rows = parsedCycles.data.map((data) => {
    const tsMs =
      (typeof data.timestamp === "number" && data.timestamp) ||
      (typeof data.ts === "number" && data.ts) ||
      (typeof data.event_timestamp === "number" && data.event_timestamp) ||
      fallbackTsMs;

    const ts = tsMs ? new Date(tsMs) : new Date();

    return {
      orgId: machine.orgId,
      machineId: machine.id,
      ts,
      cycleCount: typeof data.cycle_count === "number" ? data.cycle_count : null,
      actualCycleTime: data.actual_cycle_time,
      theoreticalCycleTime: typeof data.theoretical_cycle_time === "number" ? data.theoretical_cycle_time : null,
      workOrderId: data.work_order_id ? String(data.work_order_id) : null,
      sku: data.sku ? String(data.sku) : null,
      cavities: typeof data.cavities === "number" ? data.cavities : null,
      goodDelta: typeof data.good_delta === "number" ? data.good_delta : null,
      scrapDelta: typeof data.scrap_delta === "number" ? data.scrap_delta : null,
    };
  });

  const result = await prisma.machineCycle.createMany({
    data: rows,
    skipDuplicates: true,
  });

  if (rows.length === 1) {
    const row = await prisma.machineCycle.findFirst({
      where: {
        orgId: machine.orgId,
        machineId: machine.id,
        ts: rows[0].ts,
        cycleCount: rows[0].cycleCount ?? null,
      },
      orderBy: { createdAt: "asc" },
      select: { id: true, ts: true },
    });
    return NextResponse.json({
      ok: true,
      id: row?.id,
      ts: row?.ts,
      inserted: result.count,
      duplicate: result.count === 0,
    });
  }

  return NextResponse.json({
    ok: true,
    inserted: result.count,
    requested: rows.length,
    count: result.count,
  });
}
