import { createHash } from "node:crypto";

export function normalizeUnit(unit, { tenantId, connectionId, receivedAt = new Date() }) {
  const sourceUnitId = positiveInteger(first(unit?.id, unit?.unit_id, unit?.unitId));
  if (!sourceUnitId) return null;

  const lastMessage = firstObject(unit?.lmsg, unit?.last_msg, unit?.lastMsg, unit?.lastMessage) || {};
  const latitude = finiteNumber(first(lastMessage.lat, lastMessage.latitude, unit?.lat, unit?.latitude));
  const longitude = finiteNumber(first(lastMessage.lon, lastMessage.lng, lastMessage.longitude, unit?.lon, unit?.lng, unit?.longitude));
  const observedAt = timestamp(first(lastMessage.time, lastMessage.ts, lastMessage.timestamp, unit?.last_msg_time, unit?.lmsg_time), receivedAt);
  const telemetry = {
    sourceUnitId,
    name: optionalString(first(unit?.name, unit?.unit_name)),
    observedAt: observedAt.toISOString(),
    latitude,
    longitude,
    speed: finiteNumber(first(lastMessage.speed, unit?.speed)),
    course: finiteNumber(first(lastMessage.course, lastMessage.angle, unit?.course)),
    gpsValid: booleanOrNull(first(lastMessage.gps_valid, lastMessage.gpsValid, unit?.gps_valid)),
    satellites: finiteNumber(first(lastMessage.sats, lastMessage.satellites, unit?.sats)),
    hdop: finiteNumber(first(lastMessage.hdop, unit?.hdop)),
    accuracyM: finiteNumber(first(lastMessage.accuracy, lastMessage.accuracy_m, unit?.accuracy_m)),
  };

  return {
    tenantId,
    connectionId,
    sourceUnitId,
    stableSubjectId: `gelios.unit:${connectionId}:${sourceUnitId}`,
    name: telemetry.name || `Gelios unit ${sourceUnitId}`,
    observedAt,
    receivedAt,
    latitude,
    longitude,
    speed: telemetry.speed,
    course: telemetry.course,
    gpsValid: telemetry.gpsValid,
    satellites: telemetry.satellites,
    hdop: telemetry.hdop,
    accuracyM: telemetry.accuracyM,
    telemetry,
    fingerprint: hash(telemetry),
  };
}

export function extractUnitItems(payload) {
  if (Array.isArray(payload?.items)) return payload.items;
  if (Array.isArray(payload?.units)) return payload.units;
  if (Array.isArray(payload?.data)) return payload.data;
  return [];
}

export function hash(value) {
  return createHash("sha256").update(JSON.stringify(value)).digest("hex");
}

function first(...values) {
  return values.find((value) => value !== undefined && value !== null && value !== "");
}

function firstObject(...values) {
  return values.find((value) => value && typeof value === "object" && !Array.isArray(value));
}

function positiveInteger(value) {
  const parsed = Number.parseInt(value, 10);
  return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
}

function finiteNumber(value) {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function optionalString(value) {
  const normalized = String(value ?? "").trim();
  return normalized || null;
}

function booleanOrNull(value) {
  if (value === true || value === 1 || value === "1" || value === "true") return true;
  if (value === false || value === 0 || value === "0" || value === "false") return false;
  return null;
}

function timestamp(value, fallback) {
  const numeric = Number(value);
  if (!Number.isFinite(numeric) || numeric <= 0) return fallback;
  const milliseconds = numeric < 1_000_000_000_000 ? numeric * 1000 : numeric;
  const parsed = new Date(milliseconds);
  return Number.isNaN(parsed.getTime()) ? fallback : parsed;
}
