import { createHash } from "node:crypto";
import { execFileSync, spawn } from "node:child_process";
import { once } from "node:events";
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { request } from "node:http";
import { resolve } from "node:path";

const host = "127.0.0.1";
const port = 43121;

function sha256(value) {
  return createHash("sha256").update(value).digest("hex").toUpperCase();
}

function firewallSnapshot() {
  return execFileSync(
    "netsh",
    ["advfirewall", "firewall", "show", "rule", "name=all", "dir=in"],
    { encoding: "utf8", windowsHide: true },
  );
}

function httpRequest(headers = {}) {
  return new Promise((resolveRequest, reject) => {
    const outgoing = request({ headers, host, method: "GET", path: "/", port }, (response) => {
      response.resume();
      response.once("end", () => resolveRequest({ headers: response.headers, status: response.statusCode }));
    });
    outgoing.once("error", reject);
    outgoing.end();
  });
}

async function waitUntilReady() {
  for (let attempt = 0; attempt < 40; attempt += 1) {
    try {
      const response = await httpRequest();
      if (response.status === 200) return response;
    } catch {
      // The fixed listener may still be starting.
    }
    await new Promise((resolveWait) => setTimeout(resolveWait, 125));
  }
  throw new Error("The fixed loopback listener did not become ready.");
}

const firewallBefore = firewallSnapshot();
const child = spawn(process.execPath, ["apps/api/dist/main.js"], {
  stdio: ["ignore", "pipe", "pipe"],
  windowsHide: true,
});
let stderr = "";
child.stderr.on("data", (chunk) => {
  stderr += chunk.toString();
});

let rootResponse;
let rejectedResponse;
let listeners;
try {
  rootResponse = await waitUntilReady();
  rejectedResponse = await httpRequest({ host: "192.168.1.10:43121" });
  const netstat = execFileSync("netstat", ["-ano", "-p", "tcp"], { encoding: "utf8", windowsHide: true });
  const matchingListeners = netstat
    .split(/\r?\n/)
    .map((line) => line.trim().split(/\s+/))
    .filter((tokens) => tokens[0]?.toUpperCase() === "TCP" && tokens.at(-1) === String(child.pid) && tokens[1]?.endsWith(`:${port}`))
    .map((tokens) => ({ local_address: tokens[1], pid: child.pid }));
  listeners = [...new Map(matchingListeners.map((listener) => [listener.local_address, listener])).values()];
} finally {
  child.kill();
  await Promise.race([
    once(child, "exit"),
    new Promise((_, reject) => setTimeout(() => reject(new Error("API process did not stop.")), 5000)),
  ]);
}

if (stderr.trim()) throw new Error("The API wrote to stderr during loopback smoke.");
const firewallAfter = firewallSnapshot();
const expectedListener = `${host}:${port}`;
const socketResult = {
  expected: expectedListener,
  listeners,
  status: listeners.length > 0 && listeners.every(({ local_address }) => local_address === expectedListener)
    ? "passed"
    : "failed",
};
const responseResult = {
  invalid_host: rejectedResponse,
  localhost: rootResponse,
  status: rootResponse.status === 200 && rejectedResponse.status === 426 ? "passed" : "failed",
};
const firewallResult = {
  after_sha256: sha256(firewallAfter),
  before_sha256: sha256(firewallBefore),
  status: firewallAfter === firewallBefore ? "passed" : "failed",
  unchanged: firewallAfter === firewallBefore,
};

const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BND;
if (evidenceDirectory) {
  mkdirSync(evidenceDirectory, { recursive: true });
  writeFileSync(resolve(evidenceDirectory, "socket-listeners.json"), `${JSON.stringify(socketResult, null, 2)}\n`);
  writeFileSync(resolve(evidenceDirectory, "response.json"), `${JSON.stringify(responseResult, null, 2)}\n`);
  writeFileSync(resolve(evidenceDirectory, "firewall-diff.json"), `${JSON.stringify(firewallResult, null, 2)}\n`);
}

const result = {
  firewall: firewallResult.status,
  responses: responseResult.status,
  sockets: socketResult.status,
  status: [firewallResult.status, responseResult.status, socketResult.status].every((status) => status === "passed")
    ? "passed"
    : "failed",
};
console.log(JSON.stringify(result, null, 2));
if (result.status !== "passed") process.exit(1);
