import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
import { dirname, join } from "node:path";

const here = dirname(fileURLToPath(import.meta.url));

// Load the shared map (classic-script side-effect sets the global).
// pathToFileURL: Windows ESM import() requires a file:// URL, not a c:\ path.
await import(pathToFileURL(join(here, "dashboard-routes.js")).href);
const shared = globalThis.NEXUS_DASHBOARD_ROUTES || {};

// Extract the action keys declared inside background.js's DASHBOARD_ROUTES object.
function backgroundRouteKeys() {
  const src = readFileSync(join(here, "background.js"), "utf8");
  const start = src.indexOf("const DASHBOARD_ROUTES = {");
  assert.ok(start >= 0, "DASHBOARD_ROUTES literal found in background.js");
  const end = src.indexOf("function isDashboardAction", start); // immediately follows the object
  assert.ok(end > start, "end of DASHBOARD_ROUTES located");
  const block = src.slice(start, end);
  const keys = new Set();
  // Match both quoted ("grd-list": { method) and unquoted (today: { method)
  // keys. Prettier's quoteProps:"as-needed" strips quotes from plain-identifier
  // keys, so a quote-only regex would miss `today`/`capabilities` and report
  // false drift the moment background.js is reformatted.
  const re = /["']?([a-zA-Z0-9_$-]+)["']?:\s*\{\s*method/g;
  let m;
  while ((m = re.exec(block))) keys.add(m[1]);
  return keys;
}

test("dashboard-routes.js is a complete copy of background.js DASHBOARD_ROUTES (no drift)", () => {
  const bg = backgroundRouteKeys();
  assert.ok(bg.size > 300, `expected the full background route set, got ${bg.size}`);
  const sharedKeys = new Set(Object.keys(shared));
  // Every server-backed action the extension knows MUST be in the shared map,
  // else the /web build silently can't reach that surface. If this fails after
  // adding a route to background.js, re-copy DASHBOARD_ROUTES into dashboard-routes.js.
  const missing = [...bg].filter((k) => !sharedKeys.has(k));
  assert.deepEqual(
    missing,
    [],
    `routes in background.js missing from dashboard-routes.js: ${missing.join(", ")}`,
  );
  // …and the reverse: a route added to dashboard-routes.js (the /web copy) but
  // NOT to background.js is dead in the packaged extension (the SW dispatches
  // off background.js's own literal). Assert set EQUALITY so drift fails either way.
  const extra = [...sharedKeys].filter((k) => !bg.has(k));
  assert.deepEqual(
    extra,
    [],
    `routes in dashboard-routes.js missing from background.js (dead in the packaged extension): ${extra.join(", ")}`,
  );
});

/**
 * Key-set equality above proves both maps KNOW every route. It does not prove
 * they AGREE on where each one points. A typo'd path or a wrong method in one
 * copy passes that check and breaks exactly one surface — and since the
 * packaged extension dispatches off background.js while /web uses this shared
 * copy, the broken one is usually the extension, which is the harder of the two
 * to notice. This executes every path builder in both maps and compares.
 */
function backgroundRouteMap() {
  const src = readFileSync(join(here, "background.js"), "utf8");
  const start = src.indexOf("const DASHBOARD_ROUTES = {");
  const open = src.indexOf("{", start);
  let depth = 0;
  let end = -1;
  for (let i = open; i < src.length; i++) {
    const c = src[i];
    if (c === "{") depth++;
    else if (c === "}" && --depth === 0) {
      end = i + 1;
      break;
    }
  }
  assert.ok(end > open, "DASHBOARD_ROUTES object literal delimited");
  // The literal contains only template strings + encodeURIComponent.
  return new Function(`return (${src.slice(open, end)});`)();
}

test("both route maps resolve every action to the SAME url and method", () => {
  const bg = backgroundRouteMap();
  // One probe covering every param name the builders read; unused keys are
  // harmless, and a builder that throws is itself a failure worth reporting.
  const probe = {
    id: "X1",
    workId: "W2",
    kind: "K3",
    type: "T4",
    platform: "P5",
    operation: "O6",
    signalId: "S7",
    investorId: "I8",
    contactId: "C9",
    companyId: "CO",
    keywordId: "KW",
    accountId: "A1",
    presetId: "PR",
    token: "TK",
    slug: "SL",
    runId: "RN",
    queryString: "?q=1",
    body: { a: 1 },
  };
  const call = (fn) => {
    try {
      return String(fn(probe));
    } catch (e) {
      return `THREW: ${e.message}`;
    }
  };
  const mismatches = [];
  for (const key of Object.keys(shared)) {
    const a = bg[key];
    const b = shared[key];
    if (!a) continue; // key-set equality is the other test's job
    const pa = call(a.path);
    const pb = call(b.path);
    if (pa !== pb) mismatches.push(`${key}: background="${pa}" shared="${pb}"`);
    else if ((a.method || "GET") !== (b.method || "GET")) {
      mismatches.push(`${key}: method background=${a.method} shared=${b.method}`);
    }
  }
  assert.deepEqual(
    mismatches,
    [],
    `the two copies disagree on where these actions point — the packaged ` +
      `extension and /web would hit different endpoints:\n  ${mismatches.join("\n  ")}`,
  );
});

test("shared route entries have the expected { method, path() } shape", () => {
  const sample = ["today", "signals-list", "crm-deals", "notifications-list", "vc-queue"];
  for (const k of sample) {
    const r = shared[k];
    assert.ok(r, `shared map has ${k}`);
    assert.equal(typeof r.path, "function", `${k}.path is a function`);
    assert.equal(
      typeof r.path({ id: "1", queryString: "?x=1" }),
      "string",
      `${k}.path(m) returns a string`,
    );
  }
});
