#!/usr/bin/env node

import { appendFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
import path from "node:path";

function parseArgs(argv) {
  const args = {
    base: "http://127.0.0.1:8899",
    deviceId: "xgfe65eimrrofyws",
    intervalSeconds: 60,
    timeoutSeconds: 45,
    outputDir: "",
  };
  const mapping = {
    "--base": "base",
    "--device-id": "deviceId",
    "--interval-seconds": "intervalSeconds",
    "--timeout-seconds": "timeoutSeconds",
    "--output-dir": "outputDir",
  };
  for (let index = 0; index < argv.length; index += 2) {
    const key = mapping[argv[index]];
    if (!key || argv[index + 1] === undefined) {
      throw new Error(`invalid argument: ${argv[index] ?? ""}`);
    }
    args[key] = argv[index + 1];
  }
  args.intervalSeconds = Number(args.intervalSeconds);
  args.timeoutSeconds = Number(args.timeoutSeconds);
  if (!args.outputDir) {
    throw new Error("--output-dir is required");
  }
  return args;
}

async function requestJson(url, timeoutSeconds) {
  const response = await fetch(url, {
    headers: { Accept: "application/json", "User-Agent": "workphone-stability/2.0" },
    signal: AbortSignal.timeout(timeoutSeconds * 1000),
  });
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${url}`);
  }
  return response.json();
}

async function writeJson(filePath, value) {
  const tempPath = `${filePath}.tmp`;
  await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
  await rename(tempPath, filePath);
}

async function restoreSamples(samplesPath) {
  const state = {
    startedAt: new Date(),
    total: 0,
    ok: 0,
    failures: 0,
    offlineEvents: 0,
    reconnects: 0,
    latencyTotal: 0,
    maxLatency: 0,
    lastOk: null,
    lastError: "",
  };
  let content = "";
  try {
    content = await readFile(samplesPath, "utf8");
  } catch (error) {
    if (error.code !== "ENOENT") throw error;
    return state;
  }
  for (const line of content.split("\n")) {
    if (!line.trim()) continue;
    let row;
    try {
      row = JSON.parse(line);
    } catch {
      continue;
    }
    if (!row || typeof row !== "object") continue;
    if (state.total === 0) {
      const firstTimestamp = new Date(row.timestamp);
      if (!Number.isNaN(firstTimestamp.getTime())) state.startedAt = firstTimestamp;
    }
    const rowOk = Boolean(row.ok);
    const elapsedMs = Number(row.request_elapsed_ms) || 0;
    state.total += 1;
    state.ok += Number(rowOk);
    state.failures += Number(!rowOk);
    state.latencyTotal += elapsedMs;
    state.maxLatency = Math.max(state.maxLatency, elapsedMs);
    if (rowOk) {
      if (state.lastOk === false) state.reconnects += 1;
      state.lastError = "";
    } else {
      if (state.lastOk !== false) state.offlineEvents += 1;
      state.lastError = String(row.error || "sdk/ws/hook not ready");
    }
    state.lastOk = rowOk;
  }
  return state;
}

const args = parseArgs(process.argv.slice(2));
const outputDir = path.resolve(args.outputDir);
const samplesPath = path.join(outputDir, "stability_samples.jsonl");
const summaryPath = path.join(outputDir, "stability_latest_summary.json");
await mkdir(outputDir, { recursive: true });
const state = await restoreSamples(samplesPath);
let stopping = false;
process.on("SIGTERM", () => {
  stopping = true;
});
process.on("SIGINT", () => {
  stopping = true;
});

while (!stopping) {
  const sampleStarted = Date.now();
  const now = new Date();
  const row = {
    timestamp: now.toISOString(),
    device_id: args.deviceId,
    ok: false,
  };
  try {
    const health = await requestJson(`${args.base}/health`, args.timeoutSeconds);
    const query = new URLSearchParams({
      device_id: args.deviceId,
      samples: "1",
      interval_seconds: "0",
    });
    const watch = await requestJson(
      `${args.base}/api/v3/stability/watch?${query.toString()}`,
      args.timeoutSeconds,
    );
    const probe = Array.isArray(watch?.data?.samples) ? watch.data.samples[0] ?? {} : {};
    Object.assign(row, {
      sdk_healthy: health.status === "healthy",
      devices_online: health.devices_online,
      ws_online: Boolean(probe.ws_online),
      adb_online: Boolean(probe.adb_online),
      hook_ok: Boolean(probe.hook_ok),
      wechat_version: probe.wechat_version || "",
      probe_latency_ms: probe.latency_ms || 0,
      error: probe.error || "",
    });
    row.ok = Boolean(row.sdk_healthy && row.ws_online && row.hook_ok);
  } catch (error) {
    row.error = String(error?.message || error).slice(0, 500);
  }

  row.request_elapsed_ms = Date.now() - sampleStarted;
  state.total += 1;
  state.latencyTotal += row.request_elapsed_ms;
  state.maxLatency = Math.max(state.maxLatency, row.request_elapsed_ms);
  if (row.ok) {
    state.ok += 1;
    if (state.lastOk === false) state.reconnects += 1;
    state.lastError = "";
  } else {
    state.failures += 1;
    state.lastError = String(row.error || "sdk/ws/hook not ready");
    if (state.lastOk !== false) state.offlineEvents += 1;
  }
  state.lastOk = Boolean(row.ok);
  await appendFile(samplesPath, `${JSON.stringify(row)}\n`, "utf8");

  const elapsedSeconds = Math.max((now.getTime() - state.startedAt.getTime()) / 1000, 0);
  await writeJson(summaryPath, {
    started_at: state.startedAt.toISOString(),
    updated_at: now.toISOString(),
    elapsed_seconds: Math.trunc(elapsedSeconds),
    elapsed_hours: Number((elapsedSeconds / 3600).toFixed(3)),
    device_id: args.deviceId,
    base: args.base,
    interval_seconds: args.intervalSeconds,
    total_samples: state.total,
    ok_samples: state.ok,
    failed_samples: state.failures,
    success_rate: state.total ? Number((state.ok / state.total).toFixed(6)) : 0,
    offline_events: state.offlineEvents,
    reconnects: state.reconnects,
    average_request_elapsed_ms: state.total
      ? Number((state.latencyTotal / state.total).toFixed(2))
      : 0,
    max_request_elapsed_ms: state.maxLatency,
    last_ok: Boolean(row.ok),
    last_error: state.lastError,
    acceptance_24h_complete: elapsedSeconds >= 86400 && state.failures === 0,
    samples_file: samplesPath,
    runner: "node",
  });
  if (!stopping) {
    await new Promise((resolve) => setTimeout(resolve, Math.max(args.intervalSeconds, 5) * 1000));
  }
}
