import { afterEach, describe, expect, it, vi } from "vitest";
import type { RegexProjectV1 } from "./project.types";
import { loadMostRecentProject, saveProjectLocally } from "./persistence";

const project: RegexProjectV1 = {
  schemaVersion: 1,
  id: "project-1",
  name: "Latest",
  createdAt: "2026-07-24T00:00:00.000Z",
  updatedAt: "2026-07-24T01:00:00.000Z",
  flavour: "ecmascript",
  pattern: "a",
  flags: ["g"],
  options: {},
  replacement: "$&",
  mode: "match",
  testText: { included: false },
  listTemplate: { source: "$0", tokens: [{ kind: "full-match" }] },
  tests: [],
  ui: { live: false, scanAll: false },
};

afterEach(() => {
  vi.unstubAllGlobals();
});

describe("project persistence bounds", () => {
  it("omits unit-test subjects by default and stores them only after opt-in", async () => {
    const put = vi.fn();
    const close = vi.fn();
    const database = {
      transaction: vi.fn(() => {
        const transaction = {
          objectStore: () => ({ put }),
          error: null,
          oncomplete: null as ((event: Event) => void) | null,
          onerror: null as ((event: Event) => void) | null,
        };
        queueMicrotask(() => transaction.oncomplete?.(new Event("complete")));
        return transaction;
      }),
      close,
    };
    const open = vi.fn(() => {
      const request = {
        result: database,
        error: null,
        transaction: null,
        onupgradeneeded: null as ((event: Event) => void) | null,
        onsuccess: null as ((event: Event) => void) | null,
        onerror: null as ((event: Event) => void) | null,
      };
      queueMicrotask(() => request.onsuccess?.(new Event("success")));
      return request as unknown as IDBOpenDBRequest;
    });
    vi.stubGlobal("indexedDB", { open } as unknown as IDBFactory);
    const withSubject: RegexProjectV1 = {
      ...project,
      tests: [
        {
          id: "sensitive-test",
          name: "Sensitive",
          enabled: true,
          flavour: "ecmascript",
          pattern: "secret",
          flags: [],
          options: {},
          subject: "private subject",
          expectation: { kind: "should-match" },
        },
      ],
    };

    await saveProjectLocally(withSubject, false);
    expect(put.mock.calls[0]?.[0].tests[0].subject).toBe("");

    await saveProjectLocally(withSubject, false, true);
    expect(put.mock.calls[1]?.[0].tests[0].subject).toBe("private subject");
  });

  it("loads one latest record through the updatedAt index cursor", async () => {
    const cursorRequest = {
      result: { value: project },
      error: null,
      onsuccess: null as ((event: Event) => void) | null,
      onerror: null as ((event: Event) => void) | null,
    };
    const openCursor = vi.fn(() => {
      queueMicrotask(() => cursorRequest.onsuccess?.(new Event("success")));
      return cursorRequest as unknown as IDBRequest<IDBCursorWithValue | null>;
    });
    const index = vi.fn(() => ({ openCursor }));
    const objectStore = vi.fn(() => ({ index }));
    const close = vi.fn();
    const database = {
      transaction: vi.fn(() => ({ objectStore })),
      close,
    };
    const openRequest = {
      result: database,
      error: null,
      transaction: null,
      onupgradeneeded: null as ((event: Event) => void) | null,
      onsuccess: null as ((event: Event) => void) | null,
      onerror: null as ((event: Event) => void) | null,
    };
    const open = vi.fn(() => {
      queueMicrotask(() => openRequest.onsuccess?.(new Event("success")));
      return openRequest as unknown as IDBOpenDBRequest;
    });
    vi.stubGlobal("indexedDB", { open } as unknown as IDBFactory);

    await expect(loadMostRecentProject()).resolves.toMatchObject({
      id: "project-1",
      name: "Latest",
    });
    expect(index).toHaveBeenCalledWith("updatedAt");
    expect(openCursor).toHaveBeenCalledWith(null, "prev");
    expect(close).toHaveBeenCalledOnce();
  });

  it("rejects an aggregate oversized save before opening IndexedDB", async () => {
    const open = vi.fn();
    vi.stubGlobal("indexedDB", { open } as unknown as IDBFactory);
    const oversized: RegexProjectV1 = {
      ...project,
      tests: [
        {
          id: "oversized",
          name: "Oversized",
          enabled: true,
          flavour: "ecmascript",
          pattern: "a",
          flags: [],
          options: {},
          subject: "\0".repeat(6 * 1024 * 1024),
          expectation: { kind: "should-match" },
        },
      ],
    };

    await expect(saveProjectLocally(oversized, false, true)).rejects.toThrow(
      /32 MiB/u,
    );
    expect(open).not.toHaveBeenCalled();
  });
});
