import { prisma } from "@/lib/prisma";
import {
  buildTimelineSegments,
  compressTimelineSegments,
  TIMELINE_EVENT_TYPES,
  type TimelineCycleRow,
  type TimelineEventRow,
} from "@/lib/recap/timeline";
import type { RecapTimelineResponse } from "@/lib/recap/types";

const TIMELINE_EVENT_LOOKBACK_MS = 24 * 60 * 60 * 1000;
const TIMELINE_CYCLE_LOOKBACK_MS = 15 * 60 * 1000;
const DEFAULT_RANGE_MS = 24 * 60 * 60 * 1000;
const MIN_RANGE_MS = 60 * 1000;
const MAX_RANGE_MS = 72 * 60 * 60 * 1000;

function parseDateInput(raw: string | null) {
  if (!raw) return null;
  const asNum = Number(raw);
  if (Number.isFinite(asNum)) {
    const d = new Date(asNum);
    return Number.isFinite(d.getTime()) ? d : null;
  }
  const d = new Date(raw);
  return Number.isFinite(d.getTime()) ? d : null;
}

function parseRangeDurationMs(raw: string | null) {
  if (!raw) return null;
  const normalized = raw.trim().toLowerCase();
  const match = /^(\d+)\s*([hm])$/.exec(normalized);
  if (!match) return null;

  const amount = Number(match[1]);
  if (!Number.isFinite(amount) || amount <= 0) return null;
  const unit = match[2];
  const durationMs = unit === "m" ? amount * 60_000 : amount * 60 * 60_000;
  return Math.max(MIN_RANGE_MS, Math.min(MAX_RANGE_MS, durationMs));
}

function parseHours(raw: string | null) {
  if (!raw) return null;
  const parsed = Math.trunc(Number(raw));
  if (!Number.isFinite(parsed) || parsed <= 0) return null;
  return Math.max(1, Math.min(72, parsed));
}

function parseMaxSegments(searchParams: URLSearchParams) {
  const compact = searchParams.get("compact");
  const maxSegmentsRaw = searchParams.get("maxSegments");
  if (compact !== "1" && compact !== "true" && !maxSegmentsRaw) return null;

  const parsed = Math.trunc(Number(maxSegmentsRaw ?? "30"));
  if (!Number.isFinite(parsed) || parsed <= 0) return 30;
  return Math.max(5, Math.min(120, parsed));
}

export function parseRecapTimelineRange(searchParams: URLSearchParams) {
  const defaultEnd = new Date(Math.floor(Date.now() / 60000) * 60000);
  const end = parseDateInput(searchParams.get("end")) ?? defaultEnd;
  const startParam = parseDateInput(searchParams.get("start"));
  if (startParam && startParam < end) {
    return {
      start: startParam,
      end,
      maxSegments: parseMaxSegments(searchParams),
    };
  }

  const rangeDurationMs =
    parseRangeDurationMs(searchParams.get("range")) ??
    (() => {
      const hours = parseHours(searchParams.get("hours"));
      return hours ? hours * 60 * 60 * 1000 : null;
    })() ??
    DEFAULT_RANGE_MS;

  const start = new Date(end.getTime() - Math.max(MIN_RANGE_MS, Math.min(MAX_RANGE_MS, rangeDurationMs)));
  return {
    start,
    end,
    maxSegments: parseMaxSegments(searchParams),
  };
}

export async function getRecapTimelineForMachine(params: {
  orgId: string;
  machineId: string;
  start: Date;
  end: Date;
  maxSegments?: number | null;
}) {
  const [cyclesRaw, eventsRaw, cycleCount, eventCount] = await Promise.all([
    prisma.machineCycle.findMany({
      where: {
        orgId: params.orgId,
        machineId: params.machineId,
        ts: {
          gte: new Date(params.start.getTime() - TIMELINE_CYCLE_LOOKBACK_MS),
          lte: params.end,
        },
      },
      orderBy: { ts: "asc" },
      select: {
        ts: true,
        cycleCount: true,
        actualCycleTime: true,
        theoreticalCycleTime: true,
        workOrderId: true,
        sku: true,
      },
    }),
    prisma.machineEvent.findMany({
      where: {
        orgId: params.orgId,
        machineId: params.machineId,
        eventType: { in: TIMELINE_EVENT_TYPES as unknown as string[] },
        ts: {
          gte: new Date(params.start.getTime() - TIMELINE_EVENT_LOOKBACK_MS),
          lte: params.end,
        },
      },
      orderBy: { ts: "asc" },
      select: {
        ts: true,
        eventType: true,
        data: true,
      },
    }),
    prisma.machineCycle.count({
      where: {
        orgId: params.orgId,
        machineId: params.machineId,
        ts: {
          gte: new Date(params.start.getTime() - TIMELINE_CYCLE_LOOKBACK_MS),
          lte: params.end,
        },
      },
    }),
    prisma.machineEvent.count({
      where: {
        orgId: params.orgId,
        machineId: params.machineId,
        ts: { gte: params.start, lte: params.end },
      },
    }),
  ]);

  const hasData = cycleCount > 0 || eventCount > 0;

  const cycles: TimelineCycleRow[] = cyclesRaw.map((row) => ({
    ts: row.ts,
    cycleCount: row.cycleCount,
    actualCycleTime: row.actualCycleTime,
    theoreticalCycleTime: row.theoreticalCycleTime,
    workOrderId: row.workOrderId,
    sku: row.sku,
  }));
  const events: TimelineEventRow[] = eventsRaw.map((row) => ({
    ts: row.ts,
    eventType: row.eventType,
    data: row.data,
  }));

  let segments = hasData
    ? buildTimelineSegments({
        cycles,
        events,
        rangeStart: params.start,
        rangeEnd: params.end,
      })
    : [];

  if (hasData && params.maxSegments && params.maxSegments > 0) {
    segments = compressTimelineSegments({
      segments,
      rangeStart: params.start,
      rangeEnd: params.end,
      maxSegments: params.maxSegments,
    });
  }

  const response: RecapTimelineResponse = {
    range: {
      start: params.start.toISOString(),
      end: params.end.toISOString(),
    },
    segments,
    hasData,
    generatedAt: new Date().toISOString(),
  };

  return response;
}
