import assert from "node:assert/strict";
import { createServer } from "node:http";
import { chmod, mkdtemp, rename, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";

import {
  FOUNDRY_BINDING_CATALOG_ACTION,
  FOUNDRY_BINDING_CATALOG_PATH,
  FOUNDRY_BINDING_GRANT_SCHEMA_VERSION,
  FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION,
  FOUNDRY_BINDING_UPSERT_ACTION,
  FOUNDRY_BINDING_UPSERT_PATH,
  createFoundryBindingGrantToken,
  foundryBindingGrantFileName,
  handleFoundryBindingApiRequest,
} from "./foundry-binding-api.mjs";

const NOW = Date.parse("2026-07-15T12:00:00.000Z");
const TARGET = Object.freeze({
  applicationId: "11111111-1111-4111-8111-111111111111",
  pageId: "map-page-1",
  bindingId: "fleet-live-points",
  dataProductId: "fleet.positions.current.v1",
  slotId: "points",
  semanticTypes: ["map.moving_object"],
  fieldProjection: ["course", "name", "speed"],
});

test("Foundry binding workload API is scoped, revocable and replay-safe at its operation boundary", async (t) => {
  const grantsDir = await mkdtemp(join(tmpdir(), "nodedc-foundry-binding-grants-"));
  const token = createFoundryBindingGrantToken();
  const calls = { preflight: [], upsert: [] };
  let readerGrantReady = true;
  const options = {
    grantsDir,
    production: false,
    now: () => NOW,
    async preflightReaderGrant(input) {
      calls.preflight.push(structuredClone(input));
      return readerGrantReady;
    },
    async upsertBinding(input, actor) {
      calls.upsert.push({ input: structuredClone(input), actor: structuredClone(actor) });
      return {
        application: { privateManifestMaterial: "must-not-leak" },
        page: { unrelatedBindings: ["must-not-leak"] },
        binding: { ...input.binding, delivery: "snapshot+patch" },
        idempotency: { replayed: calls.upsert.length > 1 },
      };
    },
  };
  await writeGrant(grantsDir, token, grantFor(token));
  const server = createServer((request, response) => {
    void handleFoundryBindingApiRequest(request, response, options);
  });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
  const address = server.address();
  const baseUrl = `http://127.0.0.1:${address.port}`;
  t.after(async () => {
    await new Promise((resolve) => server.close(resolve));
    await rm(grantsDir, { recursive: true, force: true });
  });

  await t.test("catalog exposes only grant-allowlisted products", async () => {
    const response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token });
    assert.equal(response.status, 200);
    assert.equal(response.headers.get("cache-control"), "no-store, max-age=0");
    assert.equal(response.headers.get("pragma"), "no-cache");
    assert.equal(response.headers.get("vary"), "authorization");
    assert.deepEqual(response.body, {
      ok: true,
      dataProducts: [{ id: TARGET.dataProductId, semanticTypes: TARGET.semanticTypes }],
    });
  });

  await t.test("POST forwards the stable idempotency key and grant actor without leaking full manifests", async () => {
    const payload = upsertPayload();
    const first = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, {
      token,
      method: "POST",
      body: payload,
      headers: { "X-NODEDC-Actor": "attacker-supplied-actor" },
    });
    const second = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: payload });
    assert.equal(first.status, 200);
    assert.equal(second.status, 200);
    assert.equal(first.body.idempotency.replayed, false);
    assert.equal(second.body.idempotency.replayed, true);
    assert.equal(first.body.application, undefined);
    assert.equal(first.body.page, undefined);
    assert.deepEqual(calls.upsert.map((call) => call.input.idempotencyKey), [payload.idempotencyKey, payload.idempotencyKey]);
    assert.deepEqual(calls.upsert.map((call) => call.actor), [
      { actorId: "engine-agent-42", ownerKey: "workspace-42" },
      { actorId: "engine-agent-42", ownerKey: "workspace-42" },
    ]);
    assert.deepEqual(calls.preflight[0], {
      applicationId: TARGET.applicationId,
      pageId: TARGET.pageId,
      bindingId: TARGET.bindingId,
      dataProductId: TARGET.dataProductId,
      actor: { actorId: "engine-agent-42", ownerKey: "workspace-42" },
      grantId: "foundry-binding-grant-42",
    });
  });

  await t.test("action scope and reader-grant preflight fail closed", async () => {
    const catalogDeniedToken = createFoundryBindingGrantToken();
    await writeGrant(grantsDir, catalogDeniedToken, grantFor(catalogDeniedToken, {
      actions: [FOUNDRY_BINDING_UPSERT_ACTION],
    }));
    let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: catalogDeniedToken });
    assert.equal(response.status, 403);
    assert.equal(response.body.error, "foundry_binding_action_forbidden");

    const baselineUpserts = calls.upsert.length;
    readerGrantReady = false;
    response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, {
      token,
      method: "POST",
      body: upsertPayload(),
    });
    readerGrantReady = true;
    assert.equal(response.status, 409);
    assert.equal(response.body.error, "data_product_reader_grant_not_ready");
    assert.equal(calls.upsert.length, baselineUpserts);
  });

  await t.test("exact target scope rejects any changed dimension before callbacks", async () => {
    const baselinePreflight = calls.preflight.length;
    const baselineUpsert = calls.upsert.length;
    const mutations = [
      (body) => { body.applicationId = "22222222-2222-4222-8222-222222222222"; },
      (body) => { body.pageId = "map-page-2"; },
      (body) => { body.binding.id = "fleet-live-points-2"; },
      (body) => { body.binding.dataProductId = "fleet.positions.current.v2"; },
      (body) => { body.binding.slotId = "traces"; },
      (body) => { body.binding.semanticTypes = ["map.route"]; },
      (body) => { body.binding.fieldProjection = ["name"]; },
    ];
    for (const mutate of mutations) {
      const body = upsertPayload();
      mutate(body);
      const response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body });
      assert.equal(response.status, 403);
      assert.equal(response.body.error, "foundry_binding_target_forbidden");
    }
    assert.equal(calls.preflight.length, baselinePreflight);
    assert.equal(calls.upsert.length, baselineUpsert);
  });

  await t.test("wire schema enforces the canonical UUID and identifier grammar", async () => {
    const mutations = [
      (body) => { body.applicationId = "00000000-0000-0000-0000-000000000000"; },
      (body) => { body.binding.id = "Fleet-Live-Points"; },
      (body) => { body.binding.dataProductId = "Fleet.Positions.Current.V1"; },
      (body) => { body.binding.slotId = "-points"; },
      (body) => { body.binding.semanticTypes = ["Map.Moving_Object"]; },
      (body) => { body.binding.fieldProjection = ["Display_Name"]; },
      (body) => { body.idempotencyKey = "X"; },
      (body) => { body.binding.dataProductId = ` ${TARGET.dataProductId}`; },
    ];
    for (const mutate of mutations) {
      const body = upsertPayload();
      mutate(body);
      const response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body });
      assert.equal(response.status, 400);
      assert.equal(response.body.error, "foundry_binding_request_invalid");
    }
  });

  await t.test("secret material, transport scope, browser origin and shared tokens are rejected", async () => {
    const wrongSchema = upsertPayload();
    wrongSchema.schemaVersion = "nodedc.foundry.binding-upsert/v2";
    let response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: wrongSchema });
    assert.equal(response.status, 400);
    assert.equal(response.body.error, "foundry_binding_schema_version_unsupported");

    const withSecret = upsertPayload();
    withSecret.binding.token = "ndc_edprb_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: withSecret });
    assert.equal(response.status, 400);
    assert.equal(response.body.error, "foundry_binding_secret_material_forbidden");

    const withProvider = upsertPayload();
    withProvider.binding.providerId = "gelios";
    response = await request(baseUrl, FOUNDRY_BINDING_UPSERT_PATH, { token, method: "POST", body: withProvider });
    assert.equal(response.status, 400);
    assert.equal(response.body.error, "foundry_binding_transport_or_scope_forbidden");

    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, {
      token,
      headers: { Origin: "https://foundry.nodedc.ru" },
    });
    assert.equal(response.status, 403);
    assert.equal(response.body.error, "foundry_binding_browser_origin_forbidden");

    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { rawAuthorization: "Bearer shared-platform-token" });
    assert.equal(response.status, 401);
    assert.equal(response.body.error, "foundry_binding_unauthorized");

    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, {
      headers: { Cookie: "nodedc_foundry_session=browser-session" },
    });
    assert.equal(response.status, 401);
    assert.equal(response.body.error, "foundry_binding_unauthorized");
  });

  await t.test("writable and symlink grant records are rejected", async () => {
    const writableToken = createFoundryBindingGrantToken();
    await writeGrant(grantsDir, writableToken, grantFor(writableToken));
    const writablePath = join(grantsDir, foundryBindingGrantFileName(writableToken));
    await chmod(writablePath, 0o600);
    let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: writableToken });
    assert.equal(response.status, 503);
    assert.equal(response.body.error, "foundry_binding_grant_file_invalid");

    const symlinkToken = createFoundryBindingGrantToken();
    const sourcePath = join(grantsDir, `source-${randomSuffix()}`);
    await writeFile(sourcePath, `${JSON.stringify(grantFor(symlinkToken))}\n`, { mode: 0o400 });
    await chmod(sourcePath, 0o400);
    await symlink(sourcePath, join(grantsDir, foundryBindingGrantFileName(symlinkToken)));
    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: symlinkToken });
    assert.equal(response.status, 503);
    assert.equal(response.body.error, "foundry_binding_grant_file_invalid");
  });

  await t.test("grant expiry is enforced", async () => {
    const expiredToken = createFoundryBindingGrantToken();
    await writeGrant(grantsDir, expiredToken, grantFor(expiredToken, {
      issuedAt: "2026-07-14T10:00:00.000Z",
      expiresAt: "2026-07-15T11:59:59.000Z",
    }));
    const response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: expiredToken });
    assert.equal(response.status, 401);
    assert.equal(response.body.error, "foundry_binding_grant_expired");
  });

  await t.test("revocation and rotation take effect on the next request", async () => {
    let response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token });
    assert.equal(response.status, 200);
    await writeGrant(grantsDir, token, grantFor(token, { active: false }));
    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token });
    assert.equal(response.status, 401);
    assert.equal(response.body.error, "foundry_binding_grant_inactive");

    const rotatedToken = createFoundryBindingGrantToken();
    await writeGrant(grantsDir, rotatedToken, grantFor(rotatedToken));
    response = await request(baseUrl, FOUNDRY_BINDING_CATALOG_PATH, { token: rotatedToken });
    assert.equal(response.status, 200);
  });
});

function grantFor(token, overrides = {}) {
  return {
    schemaVersion: FOUNDRY_BINDING_GRANT_SCHEMA_VERSION,
    grantId: "foundry-binding-grant-42",
    tokenHash: foundryBindingGrantFileName(token),
    active: true,
    issuedAt: "2026-07-15T11:00:00.000Z",
    expiresAt: "2026-07-16T12:00:00.000Z",
    actorId: "engine-agent-42",
    ownerKey: "workspace-42",
    actions: [FOUNDRY_BINDING_CATALOG_ACTION, FOUNDRY_BINDING_UPSERT_ACTION],
    targets: [structuredClone(TARGET)],
    ...overrides,
  };
}

function upsertPayload() {
  return {
    schemaVersion: FOUNDRY_BINDING_UPSERT_SCHEMA_VERSION,
    applicationId: TARGET.applicationId,
    pageId: TARGET.pageId,
    idempotencyKey: "foundry-binding-3b7e28b52f7212a843fa5aa4809b03ea",
    binding: {
      id: TARGET.bindingId,
      dataProductId: TARGET.dataProductId,
      slotId: TARGET.slotId,
      semanticTypes: [...TARGET.semanticTypes],
      fieldProjection: [...TARGET.fieldProjection],
    },
  };
}

async function writeGrant(grantsDir, token, grant) {
  const target = join(grantsDir, foundryBindingGrantFileName(token));
  const temp = `${target}.${randomSuffix()}.tmp`;
  await writeFile(temp, `${JSON.stringify(grant)}\n`, { mode: 0o400 });
  await chmod(temp, 0o400);
  await rename(temp, target);
}

function randomSuffix() {
  return Math.random().toString(16).slice(2);
}

async function request(baseUrl, path, { token, rawAuthorization, method = "GET", body, headers = {} } = {}) {
  const response = await fetch(`${baseUrl}${path}`, {
    method,
    headers: {
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(rawAuthorization ? { Authorization: rawAuthorization } : {}),
      ...(body ? { "Content-Type": "application/json" } : {}),
      ...headers,
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  return { status: response.status, headers: response.headers, body: await response.json() };
}
