import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import type { AddressInfo } from "node:net";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import {
  probeAuthenticatedOpenClawRelay,
  resolveRelayAcceptedTokensForPort,
  resolveRelayAuthTokenForPort,
} from "./extension-relay-auth.js";
import { getFreePort } from "./test-port.js";

async function withRelayServer(
  handler: (req: IncomingMessage, res: ServerResponse) => void,
  run: (params: { port: number }) => Promise<void>,
) {
  const port = await getFreePort();
  const server = createServer(handler);
  await new Promise<void>((resolve, reject) => {
    server.listen(port, "127.0.0.1", () => resolve());
    server.once("error", reject);
  });
  try {
    const actualPort = (server.address() as AddressInfo).port;
    await run({ port: actualPort });
  } finally {
    await new Promise<void>((resolve) => server.close(() => resolve()));
  }
}

function handleNonVersionRequest(req: IncomingMessage, res: ServerResponse): boolean {
  if (req.url?.startsWith("/json/version")) {
    return false;
  }
  res.writeHead(404);
  res.end("not found");
  return true;
}

async function probeRelay(baseUrl: string, relayAuthToken: string): Promise<boolean> {
  return await probeAuthenticatedOpenClawRelay({
    baseUrl,
    relayAuthHeader: "x-openclaw-relay-token",
    relayAuthToken,
  });
}

describe("extension-relay-auth", () => {
  const TEST_GATEWAY_TOKEN = "test-gateway-token";
  let prevGatewayToken: string | undefined;

  beforeEach(() => {
    prevGatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
    process.env.OPENCLAW_GATEWAY_TOKEN = TEST_GATEWAY_TOKEN;
  });

  afterEach(() => {
    if (prevGatewayToken === undefined) {
      delete process.env.OPENCLAW_GATEWAY_TOKEN;
    } else {
      process.env.OPENCLAW_GATEWAY_TOKEN = prevGatewayToken;
    }
  });

  it("derives deterministic relay tokens per port", async () => {
    const tokenA1 = await resolveRelayAuthTokenForPort(18790);
    const tokenA2 = await resolveRelayAuthTokenForPort(18790);
    const tokenB = await resolveRelayAuthTokenForPort(18791);
    expect(tokenA1).toBe(tokenA2);
    expect(tokenA1).not.toBe(tokenB);
    expect(tokenA1).not.toBe(TEST_GATEWAY_TOKEN);
  });

  it("accepts both relay-scoped and raw gateway tokens for compatibility", async () => {
    const tokens = await resolveRelayAcceptedTokensForPort(18790);
    expect(tokens).toContain(TEST_GATEWAY_TOKEN);
    expect(tokens[0]).not.toBe(TEST_GATEWAY_TOKEN);
    expect(tokens[0]).toBe(await resolveRelayAuthTokenForPort(18790));
  });

  it("accepts authenticated openclaw relay probe responses", async () => {
    let seenToken: string | undefined;
    await withRelayServer(
      (req, res) => {
        if (handleNonVersionRequest(req, res)) {
          return;
        }
        const header = req.headers["x-openclaw-relay-token"];
        seenToken = Array.isArray(header) ? header[0] : header;
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ Browser: "OpenClaw/extension-relay" }));
      },
      async ({ port }) => {
        const token = await resolveRelayAuthTokenForPort(port);
        const ok = await probeRelay(`http://127.0.0.1:${port}`, token);
        expect(ok).toBe(true);
        expect(seenToken).toBe(token);
      },
    );
  });

  it("rejects unauthenticated probe responses", async () => {
    await withRelayServer(
      (req, res) => {
        if (handleNonVersionRequest(req, res)) {
          return;
        }
        res.writeHead(401);
        res.end("Unauthorized");
      },
      async ({ port }) => {
        const ok = await probeRelay(`http://127.0.0.1:${port}`, "irrelevant");
        expect(ok).toBe(false);
      },
    );
  });

  it("rejects probe responses with wrong browser identity", async () => {
    await withRelayServer(
      (req, res) => {
        if (handleNonVersionRequest(req, res)) {
          return;
        }
        res.writeHead(200, { "Content-Type": "application/json" });
        res.end(JSON.stringify({ Browser: "FakeRelay" }));
      },
      async ({ port }) => {
        const ok = await probeRelay(`http://127.0.0.1:${port}`, "irrelevant");
        expect(ok).toBe(false);
      },
    );
  });
});
