import { execFile } from "node:child_process";
import { createServer } from "node:http";
import { once } from "node:events";
import { resolve } from "node:path";
import { promisify } from "node:util";
import { afterEach, describe, expect, it } from "vitest";

const execFileAsync = promisify(execFile);

describe("durable V3 x402 receipt store — multi-process Redis transaction", () => {
  const server = createServer(async (request, response) => {
    if (request.method !== "POST" || request.url !== "/pipeline") {
      response.writeHead(404).end();
      return;
    }
    const chunks: Buffer[] = [];
    for await (const chunk of request) chunks.push(Buffer.from(chunk));
    const command = JSON.parse(Buffer.concat(chunks).toString("utf8"))[0] as string[];
    // The production adapter emits one EVAL command.  This tiny shared server
    // deliberately serializes the compare-and-reserve branch like Redis EVAL.
    if (command[0] !== "EVAL" || command[2] !== "4") {
      response.writeHead(400).end(JSON.stringify([{ error: "unexpected command" }]));
      return;
    }
    const keyOffset = 3;
    const argOffset = keyOffset + 4;
    const requestKey = command[keyOffset];
    const paymentKey = command[keyOffset + 1];
    const authorizationKey = command[keyOffset + 2];
    const nonceKey = command[keyOffset + 3];
    const bindingDigest = command[argOffset];
    const record = command[argOffset + 4];
    const existing = values.get(requestKey);
    let result: unknown;
    if (existing) {
      const parsed = JSON.parse(existing);
      result = parsed.bindingDigest === bindingDigest
        ? { outcome: "existing", receipt: parsed }
        : { outcome: "conflict", code: "binding_conflict" };
    } else if (values.has(paymentKey)) {
      result = { outcome: "conflict", code: "payment_replayed" };
    } else if (values.has(authorizationKey)) {
      result = { outcome: "conflict", code: "authorization_replayed" };
    } else if (values.has(nonceKey)) {
      result = { outcome: "conflict", code: "nonce_replayed" };
    } else {
      const parsed = JSON.parse(record);
      values.set(requestKey, record);
      values.set(paymentKey, parsed.receiptId);
      values.set(authorizationKey, parsed.receiptId);
      values.set(nonceKey, parsed.receiptId);
      result = { outcome: "claimed", receipt: parsed };
    }
    response.writeHead(200, { "content-type": "application/json" });
    response.end(JSON.stringify([{ result: JSON.stringify(result) }]));
  });
  const values = new Map<string, string>();

  afterEach(async () => {
    values.clear();
    if (server.listening) {
      server.close();
      await once(server, "close");
    }
  });

  it("allows only one of two operating-system processes to reserve a payment", async () => {
    server.listen(0, "127.0.0.1");
    await once(server, "listening");
    const address = server.address();
    if (!address || typeof address === "string") throw new Error("test server did not bind TCP");
    const runner = resolve(import.meta.dirname, "../../node_modules/.bin/vite-node");
    const worker = resolve(import.meta.dirname, "receipt-store-worker.ts");
    const env = { ...process.env, RECEIPT_TEST_REDIS_URL: `http://127.0.0.1:${address.port}` };
    const [left, right] = await Promise.all([
      execFileAsync(runner, [worker], { cwd: resolve(import.meta.dirname, "../.."), env, timeout: 15_000 }),
      execFileAsync(runner, [worker], { cwd: resolve(import.meta.dirname, "../.."), env, timeout: 15_000 }),
    ]);
    const results = [JSON.parse(left.stdout), JSON.parse(right.stdout)];
    expect(results.map((result) => result.outcome).sort()).toEqual(["claimed", "existing"]);
    expect(results[0].receipt.receiptId).toBe(results[1].receipt.receiptId);
  }, 20_000);
});
