import assert from "node:assert/strict";
import { execFile as execFileCallback, spawn as spawnCallback } from "node:child_process";
import { randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import path from "node:path";
import process from "node:process";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";

import { DEFAULT_CONFIG_FILE_NAME, DEFAULT_HOST_API_URL } from "./test-gateway.mjs";

const execFile = promisify(execFileCallback);
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "php2", "php3", "php4", "php5", "caddy"];

function composeArgs(projectName, args) {
  return ["compose", "-p", projectName, ...args];
}

async function resolveRootDir(scriptPath) {
  const cwd = process.cwd();

  try {
    await fs.access(path.join(cwd, "docker-compose.yml"));
    return cwd;
  } catch {
    return path.resolve(path.dirname(scriptPath), "..");
  }
}

async function runCommand(command, args, { cwd, allowFailure = false, stdio = "pipe" } = {}) {
  if (stdio === "inherit") {
    await new Promise((resolve, reject) => {
      const child = spawnCallback(command, args, {
        cwd,
        stdio: "inherit",
        windowsHide: true,
      });

      child.on("exit", (code) => {
        if (code === 0 || allowFailure) {
          resolve();
          return;
        }

        reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}`));
      });
      child.on("error", reject);
    });

    return { stdout: "", stderr: "", code: 0 };
  }

  try {
    const result = await execFile(command, args, {
      cwd,
      windowsHide: true,
      encoding: "utf8",
    });

    return { stdout: result.stdout, stderr: result.stderr, code: 0 };
  } catch (error) {
    if (!allowFailure) {
      throw error;
    }

    return {
      stdout: error.stdout || "",
      stderr: error.stderr || "",
      code: typeof error.code === "number" ? error.code : 1,
    };
  }
}

function normalizeBaseUrl(url) {
  return String(url || "").replace(/\/+$/, "");
}

function baseUrlWithHost(baseUrl, host, port = null) {
  const url = new URL(normalizeBaseUrl(baseUrl));
  url.hostname = host;
  if (port !== null) {
    url.port = port;
  }
  return normalizeBaseUrl(url.toString());
}

function directCaddyBaseUrl(baseUrl) {
  const url = new URL(normalizeBaseUrl(baseUrl));
  url.hostname = "caddy";
  url.port = "";
  if (url.pathname === "/api" || url.pathname === "/api/") {
    url.pathname = "/";
  }
  return normalizeBaseUrl(url.toString());
}

function isLocalHost(hostname) {
  const normalized = String(hostname || "").toLowerCase().replace(/^\x5b|\x5d$/g, "");
  return normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1";
}

function resolveBrokerWebSocketUrl(rawUrl, apiBaseUrl) {
  const websocketUrl = new URL(String(rawUrl));
  const apiUrl = new URL(normalizeBaseUrl(apiBaseUrl));
  const ciBrokerPort = String(process.env.EDGE_BROKER_CI_PORT || "").trim();

  if (isLocalHost(apiUrl.hostname) && websocketUrl.hostname === "edge-broker" && ciBrokerPort !== "") {
    websocketUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
    websocketUrl.hostname = apiUrl.hostname;
    websocketUrl.port = ciBrokerPort;
    websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
    return websocketUrl.toString();
  }

  if (apiUrl.hostname === "caddy" && websocketUrl.hostname === "caddy") {
    websocketUrl.hostname = "edge-broker";
    websocketUrl.port = "4300";
    websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
  }

  if (isLocalHost(apiUrl.hostname) && ["caddy", "edge-broker"].includes(websocketUrl.hostname)) {
    const brokerPath = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
    websocketUrl.protocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
    websocketUrl.hostname = apiUrl.hostname;
    websocketUrl.port = apiUrl.port;
    websocketUrl.pathname = `/api/edge-broker${brokerPath}`;
  }

  if (isLocalHost(websocketUrl.hostname)) {
    websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
  }

  return websocketUrl.toString();
}

async function readDefaultGatewayHost() {
  if (process.platform === "win32") {
    return null;
  }

  try {
    const routeTable = await fs.readFile("/proc/net/route", "utf8");
    const route = routeTable
      .split(/\r?\n/)
      .map((line) => line.trim().split(/\s+/))
      .find((fields) => fields[1] === "00000000" && /^[0-9A-Fa-f]{8}$/.test(fields[2] || ""));

    if (!route) {
      return null;
    }

    const gateway = route[2];
    const octets = [
      gateway.slice(6, 8),
      gateway.slice(4, 6),
      gateway.slice(2, 4),
      gateway.slice(0, 2),
    ].map((octet) => Number.parseInt(octet, 16));

    if (octets.some((octet) => !Number.isInteger(octet)) || octets.every((octet) => octet === 0)) {
      return null;
    }

    return octets.join(".");
  } catch {
    return null;
  }
}

function composeNetworkName(composeProject) {
  return `${composeProject}_default`;
}

async function readCurrentContainerRef() {
  if (process.platform === "win32") {
    return null;
  }

  const candidates = [];
  const envHostname = String(process.env.HOSTNAME || "").trim();
  if (envHostname !== "") {
    candidates.push(envHostname);
  }

  try {
    const hostname = (await fs.readFile("/etc/hostname", "utf8")).trim();
    if (hostname !== "") {
      candidates.push(hostname);
    }
  } catch {
    // Not running in a container with /etc/hostname available.
  }

  try {
    const cgroup = await fs.readFile("/proc/self/cgroup", "utf8");
    const matches = cgroup.match(/[0-9a-f]{64}/gi) || [];
    candidates.push(...matches);
  } catch {
    // cgroup metadata is optional in local development.
  }

  return candidates.find((candidate) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,127}$/.test(candidate)) || null;
}

async function connectCurrentContainerToComposeNetwork(rootDir, composeProject) {
  const containerRef = await readCurrentContainerRef();
  if (!containerRef) {
    return false;
  }

  const inspection = await runCommand("docker", ["inspect", containerRef], {
    cwd: rootDir,
    allowFailure: true,
  });
  if (inspection.code !== 0) {
    return false;
  }

  const networkName = composeNetworkName(composeProject);
  const connection = await runCommand("docker", ["network", "connect", networkName, containerRef], {
    cwd: rootDir,
    allowFailure: true,
  });
  const stderr = String(connection.stderr || "");
  if (connection.code === 0) {
    process.stdout.write(`Attached runner container ${containerRef} to ${networkName}.\n`);
    return true;
  }

  return /already exists|already connected/i.test(stderr);
}

async function disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject) {
  const containerRef = await readCurrentContainerRef();
  if (!containerRef) {
    return;
  }

  await runCommand("docker", ["network", "disconnect", composeNetworkName(composeProject), containerRef], {
    cwd: rootDir,
    allowFailure: true,
  });
}

async function readComposeServiceHost(rootDir, composeProject, serviceName) {
  const ps = await runCommand("docker", composeArgs(composeProject, ["ps", "-q", serviceName]), {
    cwd: rootDir,
    allowFailure: true,
  });
  const containerId = String(ps.stdout || "").trim().split(/\s+/).find(Boolean);
  const containerRefs = [
    ...(containerId ? [containerId] : []),
    serviceName,
  ];

  for (const containerRef of [...new Set(containerRefs)]) {
    const inspection = await runCommand("docker", [
      "inspect",
      "-f",
      "{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}",
      containerRef,
    ], {
      cwd: rootDir,
      allowFailure: true,
    });
    const host = String(inspection.stdout || "").trim().split(/\s+/).find((value) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value));

    if (host) {
      return host;
    }
  }

  return null;
}

async function candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork = false) {
  const normalized = normalizeBaseUrl(baseUrl);
  const candidates = [normalized];
  const url = new URL(normalized);

  if (["localhost", "127.0.0.1", "::1"].includes(url.hostname)) {
    if (rootDir && composeProject) {
      if (useComposeNetwork) {
        candidates.push(directCaddyBaseUrl(normalized));
        candidates.push(baseUrlWithHost(normalized, "traefik", ""));
      }

      const traefikHost = await readComposeServiceHost(rootDir, composeProject, "traefik");
      if (traefikHost) {
        candidates.push(baseUrlWithHost(normalized, traefikHost, ""));
      }
    }

    const gatewayHost = await readDefaultGatewayHost();
    if (gatewayHost) {
      candidates.push(baseUrlWithHost(normalized, gatewayHost));
    }

    candidates.push(baseUrlWithHost(normalized, "host.docker.internal"));
  }

  return [...new Set(candidates)];
}

async function waitForCondition(predicate, { timeoutMs = 30_000, intervalMs = 500, message = "Timed out" } = {}) {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    if (await predicate()) {
      return;
    }

    await new Promise((resolve) => setTimeout(resolve, intervalMs));
  }

  throw new Error(message);
}

async function ensureComposeServices(rootDir, composeProject) {
  await runCommand("docker", composeArgs(composeProject, ["up", "-d", ...COMPOSE_SERVICES]), {
    cwd: rootDir,
    stdio: "inherit",
  });
}

async function waitForApiReady(baseUrl, rootDir, composeProject, useComposeNetwork = false, attempts = 60) {
  const candidates = await candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork);
  let lastError = "API never responded";

  for (let attempt = 0; attempt < attempts; attempt += 1) {
    for (const root of candidates) {
      try {
        const response = await fetch(`${root}/ping`, {
          signal: AbortSignal.timeout(1000),
        });
        if (response.ok) {
          return root;
        }

        lastError = `${root}/ping returned HTTP ${response.status}`;
      } catch (error) {
        lastError = `${root}/ping failed: ${error instanceof Error ? error.message : String(error)}`;
      }
    }

    await new Promise((resolve) => setTimeout(resolve, 1000));
  }

  throw new Error(`API did not become ready at ${candidates.map((candidate) => `${candidate}/ping`).join(", ")}: ${lastError}`);
}

function parseLastJsonLine(output) {
  const lines = String(output || "")
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter(Boolean);

  for (let index = lines.length - 1; index >= 0; index -= 1) {
    try {
      return JSON.parse(lines[index]);
    } catch {
      // Continue scanning backwards for the JSON payload.
    }
  }

  throw new Error(`Unable to parse JSON from command output:\n${output}`);
}

async function runPhpFixture(rootDir, composeProject, action, payload = null) {
  const encodedPayload = payload === null
    ? ""
    : ` ${Buffer.from(JSON.stringify(payload), "utf8").toString("base64url")}`;
  const command = `cd /var/www/html && CONFIG_DB_TARGET=debug php tests/Support/EdgeGatewayE2eFixture.php ${action}${encodedPayload}`;

  const result = await runCommand("docker", composeArgs(composeProject, [
    "exec",
    "-T",
    "php1",
    "sh",
    "-lc",
    command,
  ]), {
    cwd: rootDir,
  });

  return parseLastJsonLine(result.stdout);
}

async function apiRequest(baseUrl, method, endpoint, { token = null, body = null, headers = {} } = {}) {
  const response = await fetch(`${normalizeBaseUrl(baseUrl)}${endpoint}`, {
    method,
    headers: {
      ...(body === null ? {} : { "content-type": "application/json" }),
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...headers,
    },
    body: body === null ? undefined : JSON.stringify(body),
  });

  const rawBody = await response.text();
  let json = null;

  if (rawBody !== "") {
    try {
      json = JSON.parse(rawBody);
    } catch {
      json = null;
    }
  }

  if (!response.ok) {
    const message =
      json?.data?.message ||
      json?.error ||
      rawBody ||
      `HTTP ${response.status}`;
    throw new Error(`${method} ${endpoint} failed: ${message}`);
  }

  return json;
}

async function loadWebSocketImplementation() {
  if (typeof WebSocket !== "undefined") {
    return WebSocket;
  }

  const module = await import("ws");
  return module.default;
}

function onSocket(socket, eventName, handler) {
  if (typeof socket.addEventListener === "function") {
    socket.addEventListener(eventName, (event) => {
      if (eventName === "message") {
        handler(event.data);
        return;
      }

      handler(event);
    });
    return;
  }

  socket.on(eventName, handler);
}

function collectSocketMessages(socket) {
  const messages = [];

  onSocket(socket, "message", (payload) => {
    const text = typeof payload === "string"
      ? payload
      : Buffer.isBuffer(payload)
        ? payload.toString("utf8")
        : typeof payload?.toString === "function"
          ? payload.toString()
          : "";

    if (text === "") {
      return;
    }

    try {
      messages.push(JSON.parse(text));
    } catch {
      // Ignore non-JSON frames.
    }
  });

  return messages;
}

async function waitForSocketOpen(socket) {
  if (socket.readyState === 1) {
    return;
  }

  await new Promise((resolve, reject) => {
    const timeout = setTimeout(() => reject(new Error("Timed out waiting for websocket open.")), 10_000);
    const socketUrl = typeof socket.url === "string" && socket.url !== "" ? ` (${socket.url})` : "";

    const onOpen = () => {
      clearTimeout(timeout);
      resolve();
    };

    const onError = (error) => {
      clearTimeout(timeout);
      if (error instanceof Error) {
        reject(error);
        return;
      }

      const readyState = typeof socket.readyState === "number" ? socket.readyState : "unknown";
      reject(new Error(`Websocket failed to open${socketUrl}; readyState=${readyState}.`));
    };

    const onClose = (event) => {
      clearTimeout(timeout);
      const code = event && typeof event === "object" && "code" in event ? event.code : "unknown";
      const reason = event && typeof event === "object" && "reason" in event ? event.reason : "";
      reject(new Error(`Websocket closed before open${socketUrl}; code=${code} reason=${reason || "none"}.`));
    };

    onSocket(socket, "open", onOpen);
    onSocket(socket, "error", onError);
    onSocket(socket, "close", onClose);
  });
}

async function waitForSocketMessage(messages, predicate, options) {
  await waitForCondition(() => messages.some(predicate), options);
}

function buildSocketUrl(wsUrl, token) {
  const url = new URL(String(wsUrl));
  url.searchParams.set("token", token);
  return url.toString();
}

function closeSocket(socket) {
  if (!socket || typeof socket.close !== "function") {
    return;
  }

  const readyState = typeof socket.readyState === "number" ? socket.readyState : null;
  if (readyState !== null && readyState >= 2) {
    return;
  }

  socket.close();
}

function shouldCopyGatewayConfig() {
  return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_COPY_CONFIG || "").trim());
}

function shouldSkipComposeUp() {
  return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP || "").trim());
}

function collectMessages(rows) {
  return Array.isArray(rows)
    ? rows
      .map((row) => (row && typeof row === "object" ? row.message : null))
      .filter((value) => typeof value === "string")
    : [];
}

function summarizeStreamMessages(messages, limit = 12) {
  return messages
    .slice(-limit)
    .map((message) => {
      if (!message || typeof message !== "object") {
        return null;
      }

      const summary = {
        type: message.type || "unknown",
      };

      if (message.operationId !== undefined) {
        summary.operationId = Number(message.operationId || 0);
      }

      if (message.operation && typeof message.operation === "object") {
        summary.operationStatus = message.operation.status || null;
      }

      if (message.gateway && typeof message.gateway === "object") {
        summary.gatewayStatus = message.gateway.status || null;
      }

      return summary;
    })
    .filter(Boolean);
}

async function readGatewayDiagnostics({ baseUrl, authToken, gatewayId, containerName }) {
  const diagnostics = {};

  if (gatewayId !== null && gatewayId > 0 && authToken) {
    try {
      const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
        token: authToken,
      });
      diagnostics.gateway = {
        status: detail?.data?.status ?? null,
        channelStatus: detail?.data?.channel_status ?? null,
        brokerPresence: detail?.data?.metadata?.broker_presence ?? null,
        brokerConnected: detail?.data?.metadata?.broker_connected ?? null,
        brokerLastError: detail?.data?.metadata?.broker_last_error ?? null,
      };
    } catch (error) {
      diagnostics.gatewayError = error instanceof Error ? error.message : String(error);
    }
  }

  try {
    const logs = await runCommand("docker", ["logs", "--tail", "120", containerName], {
      allowFailure: true,
    });
    diagnostics.containerLogs = String(`${logs.stdout || ""}${logs.stderr || ""}`).trim().split(/\r?\n/).slice(-120);
  } catch (error) {
    diagnostics.containerLogError = error instanceof Error ? error.message : String(error);
  }

  return diagnostics;
}

async function main() {
  const scriptPath = fileURLToPath(import.meta.url);
  const rootDir = await resolveRootDir(scriptPath);
  const runId = randomUUID().slice(0, 8);
  const containerName = `truckwash-edge-e2e-${runId}`;
  const configDir = path.join(rootDir, ".tmp", "edge-gateway-e2e", runId);
  const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
  let baseUrl = process.env.EDGE_GATEWAY_E2E_BASE_URL || DEFAULT_HOST_API_URL;
  const composeProject =
    process.env.EDGE_GATEWAY_E2E_COMPOSE_PROJECT
    || path.basename(rootDir);

  let fixture = null;
  let gatewayId = null;
  let streamSocket = null;
  let shellSocket = null;
  let runnerNetworkAttached = false;

  try {
    if (!shouldSkipComposeUp()) {
      await ensureComposeServices(rootDir, composeProject);
    }
    runnerNetworkAttached = await connectCurrentContainerToComposeNetwork(rootDir, composeProject);
    baseUrl = await waitForApiReady(baseUrl, rootDir, composeProject, runnerNetworkAttached);
    process.stdout.write(`Using API base URL ${baseUrl}\n`);

    fixture = await runPhpFixture(rootDir, composeProject, "create");
    const authToken = String(fixture.auth_token || "");
    const departmentId = Number(fixture.department_id || 0);

    assert.ok(authToken !== "", "Fixture helper did not return an auth token.");
    assert.ok(departmentId > 0, "Fixture helper did not return a department id.");

    const installTokenResponse = await apiRequest(baseUrl, "POST", "/edge-gateways/install-token", {
      token: authToken,
      body: {
        department_id: departmentId,
        label: `Edge Gateway E2E ${runId}`,
      },
    });
    const installToken = String(installTokenResponse?.data?.token || "");
    assert.ok(installToken !== "", "Install token creation did not return a token.");

    await runCommand(process.execPath, [
      "scripts/test-gateway.mjs",
      "start",
      "--install-token",
      installToken,
      "--host-api-url",
      baseUrl,
      "--container-name",
      containerName,
      "--config-dir",
      configDir,
      "--heartbeat-seconds",
      "3",
      "--skip-compose-up",
      ...(shouldCopyGatewayConfig() ? ["--copy-config"] : []),
    ], {
      cwd: rootDir,
      stdio: "inherit",
    });

    await waitForCondition(
      async () => {
        try {
          await fs.access(configFilePath);
          return true;
        } catch {
          return false;
        }
      },
      { message: `Gateway config file was not created at ${configFilePath}` }
    );

    const config = JSON.parse(await fs.readFile(configFilePath, "utf8"));
    gatewayId = Number(config.gatewayId || 0);
    assert.ok(gatewayId > 0, "Gateway config did not include a gateway id.");

    await waitForCondition(
      async () => {
        const result = await runCommand("docker", [
          "exec",
          containerName,
          "test",
          "-f",
          "/opt/truckwash-edge-agent/runtime/last-heartbeat-ok.txt",
        ], {
          allowFailure: true,
        });

        return result.code === 0;
      },
      {
        timeoutMs: 60_000,
        message: "Gateway never wrote the successful heartbeat marker.",
      }
    );

    await waitForCondition(
      async () => {
        const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
          token: authToken,
        });

        return detail?.data?.status === "ONLINE"
          && Object.keys(detail?.data?.metadata?.system_metrics || {}).length > 0;
      },
      {
        timeoutMs: 60_000,
        message: "Gateway detail never transitioned to ONLINE with fresh system metrics after install.",
      }
    );

    try {
      await waitForCondition(
        async () => {
          const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
            token: authToken,
          });

          return Boolean(
            detail?.data?.channel_status?.broker?.connected
            || detail?.data?.metadata?.broker_connected
          );
        },
        {
          timeoutMs: 90_000,
          message: "Gateway never established a live broker connection after install.",
        }
      );
    } catch (error) {
      const diagnostics = await readGatewayDiagnostics({
        baseUrl,
        authToken,
        gatewayId,
        containerName,
      });
      throw new Error([
        error instanceof Error ? error.message : String(error),
        `Broker diagnostics: ${JSON.stringify(diagnostics, null, 2)}`,
      ].join("\n"));
    }

    const WebSocketImpl = await loadWebSocketImplementation();
    const streamSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/stream-session`, {
      token: authToken,
      body: {
        scopes: ["overview", "tasks", "logs", "statistics"],
      },
    });
    const streamWsUrl = buildSocketUrl(
      resolveBrokerWebSocketUrl(String(streamSession?.data?.ws_url || ""), baseUrl),
      String(streamSession?.data?.token || "")
    );
    streamSocket = new WebSocketImpl(streamWsUrl);
    const streamMessages = collectSocketMessages(streamSocket);

    await waitForSocketOpen(streamSocket);
    await waitForSocketMessage(
      streamMessages,
      (message) => message?.type === "gateway.stream.ready",
      { timeoutMs: 15_000, message: "Gateway stream never became ready." }
    );

    await waitForSocketMessage(
      streamMessages,
      (message) => (
        message?.type === "gateway.stream.ready" && message?.connected === true
      ) || (
        message?.type === "presence.changed" && message?.status === "connected"
      ),
      { timeoutMs: 45_000, message: "Gateway stream never observed a connected broker presence." }
    );

    const operationResponse = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/operations`, {
      token: authToken,
      body: {
        type: "DISCOVERY",
        request: {
          inventory: [{
            device_id: `edge-e2e-${runId}`,
            local_ip: "10.70.80.90",
            model: "TruckWash Edge E2E",
            channel_count: 1,
            online: true,
            capabilities: {
              gateway_management_v2: true,
            },
            metadata: {
              hostname: `edge-e2e-${runId}`,
            },
          }],
        },
      },
    });
    const operationId = Number(operationResponse?.data?.operation?.id || 0);
    assert.ok(operationId > 0, "Operation creation did not return an operation id.");

    try {
      await waitForCondition(
        async () => {
          const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
            token: authToken,
          });

          const operation = Array.isArray(operations?.data)
            ? operations.data.find((item) => Number(item?.id || 0) === operationId)
            : null;

          return operation?.status === "COMPLETED"
            || streamMessages.some((message) => (
              message?.type === "task.updated"
              && Number(message?.operationId || 0) === operationId
              && message?.operation?.status === "COMPLETED"
            ));
        },
        { timeoutMs: 180_000, message: "Gateway operation never completed through the live agent." }
      );
    } catch (error) {
      let operationSnapshot;
      try {
        const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
          token: authToken,
        });
        operationSnapshot = Array.isArray(operations?.data)
          ? operations.data.find((item) => Number(item?.id || 0) === operationId) || null
          : null;
      } catch {
        operationSnapshot = null;
      }

      const diagnostic = [
        error instanceof Error ? error.message : String(error),
        `Recent stream messages: ${JSON.stringify(summarizeStreamMessages(streamMessages))}`,
        `Operation snapshot: ${JSON.stringify(operationSnapshot)}`,
      ].join("\n");
      throw new Error(diagnostic);
    }

    await waitForSocketMessage(
      streamMessages,
      (message) => message?.type === "gateway.telemetry" || message?.type === "stats.updated",
      { timeoutMs: 15_000, message: "Live gateway stream never emitted telemetry or statistics updates." }
    );

    await waitForCondition(
      async () => {
        const logs = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
          token: authToken,
        });

        const timeline = Array.isArray(logs?.data?.entries)
          ? logs.data.entries
          : (Array.isArray(logs?.data?.timeline) ? logs.data.timeline : []);

        return timeline.some((entry) => {
          const nestedEntry = entry?.entry && typeof entry.entry === "object" ? entry.entry : null;
          const directOperationId = Number(nestedEntry?.operation_id || 0);
          const contextualOperationId = Number(nestedEntry?.context?.operation_id || 0);

          return directOperationId === operationId || contextualOperationId === operationId;
        });
      },
      { timeoutMs: 30_000, message: "Gateway logs page never reflected the live operation timeline." }
    );

    const statistics = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/statistics`, {
      token: authToken,
    });
    assert.ok(
      Object.keys(statistics?.data?.system_metrics || {}).length > 0,
      "Gateway statistics page did not expose system metrics after live telemetry."
    );

    const shellSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/shell-sessions`, {
      token: authToken,
      body: {
        reason: "Edge gateway E2E shell validation",
        cwd: "/opt/truckwash-edge-agent",
        cols: 120,
        rows: 40,
      },
    });
    const shellWsUrl = buildSocketUrl(
      resolveBrokerWebSocketUrl(String(shellSession?.data?.ws_url || ""), baseUrl),
      String(shellSession?.data?.token || "")
    );
    shellSocket = new WebSocketImpl(shellWsUrl);
    const shellMessages = collectSocketMessages(shellSocket);

    await waitForSocketOpen(shellSocket);
    await waitForSocketMessage(
      shellMessages,
      (message) => message?.type === "opened",
      { timeoutMs: 20_000, message: "Browser shell never opened against the live gateway." }
    );

    shellSocket.send(JSON.stringify({
      type: "input",
      data: "printf 'edge-e2e-shell\\n'; exit\n",
    }));

    await waitForSocketMessage(
      shellMessages,
      (message) => message?.type === "output" && String(message?.data || "").includes("edge-e2e-shell"),
      { timeoutMs: 20_000, message: "Browser shell never returned the expected command output." }
    );

    await waitForSocketMessage(
      shellMessages,
      (message) => message?.type === "closed",
      { timeoutMs: 20_000, message: "Browser shell never closed cleanly." }
    );

    await waitForCondition(
      async () => {
        const logsAfterShell = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
          token: authToken,
        });
        const shellTranscripts = Array.isArray(logsAfterShell?.data?.shell_sessions)
          ? logsAfterShell.data.shell_sessions.map((session) => String(session?.transcript || ""))
          : [];
        const timelineMessages = collectMessages(logsAfterShell?.data?.timeline || []);

        return shellTranscripts.some((transcript) => transcript.includes("edge-e2e-shell"))
          && timelineMessages.includes("GATEWAY_SHELL_SESSION_CLOSED");
      },
      { timeoutMs: 30_000, message: "Gateway logs page did not persist the shell transcript and close audit event." }
    );

    process.stdout.write("Edge gateway E2E smoke completed successfully.\n");
  } finally {
    closeSocket(shellSocket);
    closeSocket(streamSocket);

    await runCommand(process.execPath, [
      "scripts/test-gateway.mjs",
      "stop",
      "--container-name",
      containerName,
    ], {
      cwd: rootDir,
      allowFailure: true,
    }).catch(() => {});

    const fixtureAuthToken = fixture?.auth_token;
    if (gatewayId !== null && fixtureAuthToken) {
      await apiRequest(baseUrl, "DELETE", `/edge-gateways/${gatewayId}`, {
        token: String(fixtureAuthToken),
      }).catch(() => {});
    }

    if (fixture !== null) {
      await runPhpFixture(rootDir, composeProject, "cleanup", fixture).catch(() => {});
    }

    await fs.rm(configDir, { recursive: true, force: true }).catch(() => {});

    if (runnerNetworkAttached) {
      await disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject).catch(() => {});
    }
  }
}

main().catch((error) => {
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
  process.exitCode = 1;
});
