/**
 * A `["*"]` key end to end: the public form's key, which has no allowlist.
 *
 * Offline like the rest of this directory — a fake binary, and an injected
 * resolver so a test says what a name resolves to instead of asking the world.
 */

import { afterAll, beforeAll, expect, test } from "bun:test";
import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";

import { parseKeys, start, type Service } from "./server.ts";

const REPORT_OK = '{"tool":"headless-seo-check","fatal":null,"summary":{"blocking":0},"exit_code":0}';

/** `public` audits anything; `acme` keeps the strict allowlist, side by side. */
const KEYS = { public: ["*"], acme: ["https://ok.test"] };

/** What each name resolves to. Absent means NXDOMAIN. */
const DNS: Record<string, string[]> = {
  "ok.test": ["93.184.216.34"],
  "customer.example": ["93.184.216.34"],
  "sneaky.example": ["127.0.0.1"],
  "metadata.example": ["169.254.169.254"],
  "mixed.example": ["93.184.216.34", "10.0.0.5"],
};

let directory: string;
let service: Service;
let base: string;

beforeAll(() => {
  directory = mkdtempSync(join(tmpdir(), "hsc-public-"));
  const binary = join(directory, "fake");
  const keysPath = join(directory, "keys.json");
  writeFileSync(binary, `#!/bin/sh\necho '${REPORT_OK}'\nexit 0\n`, "utf8");
  chmodSync(binary, 0o755);
  writeFileSync(keysPath, JSON.stringify(KEYS), "utf8");
  service = start({
    bin: binary,
    keysPath,
    host: "127.0.0.1",
    port: 0,
    timeoutMs: 10_000,
    maxConcurrent: 2,
    resolve: (host) => {
      const addresses = DNS[host];
      return addresses
        ? Promise.resolve(addresses.map((address) => ({ address })))
        : Promise.reject(new Error("ENOTFOUND"));
    },
  });
  base = `http://127.0.0.1:${service.port}`;
});

afterAll(async () => {
  await service.stop();
  rmSync(directory, { recursive: true, force: true });
});

function audit(site: string, key = "public"): Promise<Response> {
  return fetch(`${base}/audit`, {
    method: "POST",
    headers: { "content-type": "application/json", "x-hsc-site": key },
    body: JSON.stringify({ site, sample: 5 }),
  });
}

test("a wildcard key audits a site nobody listed", async () => {
  const response = await audit("https://customer.example");
  expect(response.status).toBe(200);
  // The service hands the binary's stdout back verbatim, trailing newline
  // included; that is the contract, so the test trims rather than the service.
  expect((await response.text()).trim()).toBe(REPORT_OK);
});

test("a wildcard key still cannot reach the loopback", async () => {
  const response = await audit("https://sneaky.example");
  expect(response.status).toBe(400);
  expect((await response.json()).error).toContain("the loopback");
});

test("a wildcard key cannot reach cloud metadata", async () => {
  // The address that turns an SSRF into stolen instance credentials.
  const response = await audit("https://metadata.example");
  expect(response.status).toBe(400);
  expect((await response.json()).error).toContain("link-local");
});

test("one private answer among public ones is refused", async () => {
  const response = await audit("https://mixed.example");
  expect(response.status).toBe(400);
});

test("a literal private address is refused before any lookup", async () => {
  for (const site of ["http://127.0.0.1:8169", "http://169.254.169.254", "http://192.168.1.1"]) {
    expect((await audit(site)).status).toBe(400);
  }
});

test("localhost is refused whatever the resolver would have said", async () => {
  expect((await audit("http://localhost:3000")).status).toBe(400);
});

test("a name that does not resolve is refused, not attempted", async () => {
  expect((await audit("https://nowhere.example")).status).toBe(400);
});

test("the wildcard does not leak into the strict key beside it", async () => {
  // The regression that would matter most: one permissive entry in keys.json
  // must not turn every other key into an open proxy.
  expect((await audit("https://ok.test", "acme")).status).toBe(200);
  expect((await audit("https://customer.example", "acme")).status).toBe(403);
});

test("a wildcard mixed with named origins is refused at load", () => {
  // "these, plus anything" is "anything" with the fact hidden.
  expect(() => parseKeys('{"x":["*","https://a.test"]}')).toThrow(/already means every one/);
  expect(() => parseKeys('{"x":["*"]}')).not.toThrow();
});
