// Guard + behaviour tests for the God-Mode structured-export route handler.
//
// The handler is NOT behind the (superadmin) layout, so it must assert the
// `export:run` sys-permission ITSELF and fail-closed (403) when that throws
// (Task 9). We module-mock the gate, the audit helper and the DB-touching
// query module at top level so the route can be exercised without a database
// or a session.
//
// WHY ITS OWN DIRECTORY: bun installs every top-level mock.module into ONE
// global registry per test-run *process*. The sibling src/lib/admin/audit.test.ts
// imports the REAL @/lib/auth/sys-permissions (Task 9) + @/lib/admin/audit;
// mocking them here would clobber it. test:all runs one bun process PER
// directory, so living under export-management/ keeps these mocks isolated
// (mirrors user-management/ etc.).
import { beforeEach, describe, expect, mock, test } from "bun:test";

let sysPermPasses = true;
const assertSysPermission = mock(async (_perm: string) => {
  if (!sysPermPasses) throw new Error("Forbidden: missing sys permission");
});
const recordAdminAction = mock(async (_input: unknown) => {});

const PAYLOAD = {
  columns: ["id", "email"],
  rows: [
    { id: "u1", email: "a@x.io" },
    { id: "u2", email: "b@x.io" },
  ],
};
const KNOWN = new Set(["users", "organizations", "subscriptions", "audit"]);

mock.module("server-only", () => ({}));
// NOTE: `bun test <dir>` recurses into subdirectories, so the src/lib/admin
// bucket (test-all.ts) shares ONE process with every admin/**/*.test.ts file;
// whichever mock.module("@/lib/auth/sys-permissions", …) registers LAST wins
// for all of them. org-actions.ts also needs `getSysOperatorSession` — export
// it here too (even though route.ts itself doesn't call it) so this mock can
// never clobber a sibling file that does.
const getSysOperatorSession = async () => ({
  userId: "admin1",
  email: "admin@example.invalid",
  kind: "admin-sys" as const,
  twoFactorEnabled: true,
});
mock.module("@/lib/auth/sys-permissions", () => ({
  assertSysPermission,
  getSysOperatorSession,
}));
mock.module("@/lib/admin/audit", () => ({ recordAdminAction }));
mock.module("@/lib/admin/export-queries", () => ({
  isExportDataset: (v: string) => KNOWN.has(v),
  buildExportPayload: async () => PAYLOAD,
}));

const ROUTE = "@/app/(superadmin)/admin/export/[dataset]/route";

function call(dataset: string, format?: string) {
  const url = `http://localhost/admin/export/${dataset}${
    format ? `?format=${format}` : ""
  }`;
  return { url, params: Promise.resolve({ dataset }) };
}

describe("export route handler", () => {
  beforeEach(() => {
    sysPermPasses = true;
    recordAdminAction.mockClear();
    assertSysPermission.mockClear();
  });

  test("returns 403 and does NOT export when the sys-permission gate throws", async () => {
    sysPermPasses = false;
    const { GET } = await import(ROUTE);
    const { url, params } = call("users", "csv");
    const res = await GET(new Request(url), { params });
    expect(res.status).toBe(403);
    expect(await res.text()).toBe("Forbidden");
    expect(recordAdminAction).not.toHaveBeenCalled();
  });

  test("authorized CSV: 200, text/csv, attachment filename, audited", async () => {
    const { GET } = await import(ROUTE);
    const { url, params } = call("users", "csv");
    const res = await GET(new Request(url), { params });

    expect(assertSysPermission.mock.calls[0]?.[0]).toBe("export:run");
    expect(res.status).toBe(200);
    expect(res.headers.get("Content-Type")).toBe("text/csv; charset=utf-8");
    const cd = res.headers.get("Content-Disposition") ?? "";
    expect(cd).toMatch(/^attachment; filename="users-\d{4}-\d{2}-\d{2}\.csv"$/);
    expect(await res.text()).toBe("id,email\r\nu1,a@x.io\r\nu2,b@x.io");

    expect(recordAdminAction).toHaveBeenCalledTimes(1);
    const arg = recordAdminAction.mock.calls[0][0] as {
      action: string;
      targetType: string;
      metadata: { format: string; rowCount: number };
    };
    expect(arg.action).toBe("export.users");
    expect(arg.targetType).toBe("system");
    expect(arg.metadata).toEqual({ format: "csv", rowCount: 2 });
  });

  test("authorized JSON: 200, application/json, json filename, JSON body", async () => {
    const { GET } = await import(ROUTE);
    const { url, params } = call("subscriptions", "json");
    const res = await GET(new Request(url), { params });

    expect(res.status).toBe(200);
    expect(res.headers.get("Content-Type")).toBe(
      "application/json; charset=utf-8",
    );
    expect(res.headers.get("Content-Disposition") ?? "").toMatch(
      /filename="subscriptions-\d{4}-\d{2}-\d{2}\.json"$/,
    );
    expect(await res.json()).toEqual(PAYLOAD.rows);
  });

  test("defaults to CSV when no format is given", async () => {
    const { GET } = await import(ROUTE);
    const { url, params } = call("users");
    const res = await GET(new Request(url), { params });
    expect(res.headers.get("Content-Type")).toBe("text/csv; charset=utf-8");
  });

  test("returns 404 for an unknown dataset (and does not audit)", async () => {
    const { GET } = await import(ROUTE);
    const { url, params } = call("bogus", "csv");
    const res = await GET(new Request(url), { params });
    expect(res.status).toBe(404);
    expect(recordAdminAction).not.toHaveBeenCalled();
  });
});
