import assert from "node:assert/strict";
import { connect } from "node:net";
import test from "node:test";

import { createDeviceGatewayRuntime } from "../src/runtime.mjs";

const specificationHeader = Buffer.from(
  "FF23E9EF782DE7120300",
  "hex",
);
const specificationPackage = Buffer.from(
  "5B01010000FBDEC251EC5D",
  "hex",
);

test("baseline health exposes no public ingress and no command transport", async () => {
  const runtime = createDeviceGatewayRuntime({
    healthPort: 0,
    listenEnabled: false,
  });
  const addresses = await runtime.start();
  try {
    const response = await fetch(
      `http://127.0.0.1:${addresses.healthAddress.port}/healthz`,
    );
    assert.equal(response.status, 200);
    const body = await response.json();
    assert.equal(body.publicIngress, "disabled");
    assert.equal(body.commandTransport, "disabled");
    assert.equal(body.tcpListener, "disabled");
    assert.equal(addresses.tcpAddress, null);
  } finally {
    await runtime.stop();
  }
});

test("discovery-only ingress persists HEADER2 before acknowledging packages", async () => {
  const captured = [];
  const runtime = createDeviceGatewayRuntime({
    healthPort: 0,
    tcpHost: "0.0.0.0",
    tcpPort: 0,
    listenEnabled: true,
    publicIngressEnabled: true,
    now: () => new Date(0x52db95de * 1000),
    onDiscovery: async (value) => captured.push(value),
  });
  const addresses = await runtime.start();
  const client = await connectAndCollect(addresses.tcpAddress.port);
  try {
    client.socket.write(specificationHeader.subarray(0, 4));
    await new Promise((resolve) => setImmediate(resolve));
    assert.equal(client.bytes().length, 0);

    client.socket.write(specificationHeader.subarray(4));
    await client.waitForBytes(9);
    assert.equal(
      client.bytes().subarray(0, 9).toString("hex").toUpperCase(),
      "7B0400A0DE95DB527D",
    );
    assert.equal(captured.length, 1);
    assert.equal(captured[0].identifier.value, "865209039777769");
    assert.equal(captured[0].evidence.framingStatus, "verified");
    assert.equal(captured[0].commandTransport, undefined);

    client.socket.write(specificationPackage);
    await client.waitForBytes(13);
    assert.equal(
      client.bytes().subarray(9).toString("hex").toUpperCase(),
      "7B00017D",
    );
    assert.equal(runtime.status().totalDiscoveries, 1);
    assert.equal(runtime.status().totalPackagesAcknowledged, 1);
    assert.equal(runtime.status().commandTransport, "disabled");
    assert.equal(runtime.status().publicIngress, "discovery-only");

    const response = await fetch(
      `http://127.0.0.1:${addresses.healthAddress.port}/healthz`,
    );
    const body = await response.json();
    assert.equal(body.framing, "verified-read-only");
    assert.equal(body.tcpListener, "discovery-only");
    assert.equal(body.publicIngress, "discovery-only");
    assert.equal(body.commandTransport, "disabled");
  } finally {
    client.socket.destroy();
    await runtime.stop();
  }
});

test("does not acknowledge malformed or unverified initial bytes", async () => {
  const captured = [];
  const runtime = createDeviceGatewayRuntime({
    healthPort: 0,
    tcpPort: 0,
    listenEnabled: true,
    onDiscovery: async (value) => captured.push(value),
  });
  const addresses = await runtime.start();
  try {
    const received = await sendAndCollect(
      addresses.tcpAddress.port,
      Buffer.from("not-a-b2-header", "utf8"),
    );
    assert.equal(received.length, 0);
    assert.equal(captured.length, 0);
    assert.equal(runtime.status().totalRejected, 1);
  } finally {
    await runtime.stop();
  }
});

test("public ingress requires an authenticated discovery sink", () => {
  assert.throws(
    () => createDeviceGatewayRuntime({
      listenEnabled: true,
      publicIngressEnabled: true,
      tcpHost: "0.0.0.0",
    }),
    /device_gateway_discovery_sink_required/,
  );
});

test("baseline rejects non-loopback binding", () => {
  assert.throws(
    () => createDeviceGatewayRuntime({
      listenEnabled: true,
      tcpHost: "0.0.0.0",
    }),
    /device_gateway_baseline_loopback_only/,
  );
});

test("container health may bind all interfaces while TCP stays disabled", async () => {
  const runtime = createDeviceGatewayRuntime({
    healthHost: "0.0.0.0",
    healthPort: 0,
    listenEnabled: false,
  });
  const addresses = await runtime.start();
  try {
    assert.equal(addresses.healthAddress.address, "0.0.0.0");
    assert.equal(addresses.tcpAddress, null);
    assert.equal(runtime.status().publicIngress, "disabled");
  } finally {
    await runtime.stop();
  }
});

function connectAndCollect(port) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    let byteLength = 0;
    const waiters = [];
    const socket = connect({ host: "127.0.0.1", port }, () => {
      resolve({
        socket,
        bytes: () => Buffer.concat(chunks, byteLength),
        waitForBytes: (minimum) => {
          if (byteLength >= minimum) return Promise.resolve();
          return new Promise((waitResolve, waitReject) => {
            waiters.push({ minimum, waitResolve, waitReject });
          });
        },
      });
    });
    socket.on("data", (chunk) => {
      chunks.push(chunk);
      byteLength += chunk.length;
      for (let index = waiters.length - 1; index >= 0; index -= 1) {
        if (byteLength >= waiters[index].minimum) {
          waiters[index].waitResolve();
          waiters.splice(index, 1);
        }
      }
    });
    socket.on("error", (error) => {
      for (const waiter of waiters.splice(0)) waiter.waitReject(error);
      reject(error);
    });
  });
}

function sendAndCollect(port, payload) {
  return new Promise((resolve, reject) => {
    const chunks = [];
    const socket = connect({ host: "127.0.0.1", port }, () => {
      socket.end(payload);
    });
    socket.on("data", (chunk) => chunks.push(chunk));
    socket.on("close", () => resolve(Buffer.concat(chunks)));
    socket.on("error", reject);
  });
}
