import { mkdirSync } from "node:fs";
import { join, resolve } from "node:path";

export interface TransactionalDatabase {
  tx<T>(callback: (transaction: TransactionalDatabase) => Promise<T>): Promise<T>;
}

const ROLLBACK = Symbol("wrnexus-test-rollback");

/** Run test work in a real transaction and always force rollback. */
export async function withDatabaseRollback<T>(
  db: TransactionalDatabase,
  run: (transaction: TransactionalDatabase) => T | Promise<T>,
): Promise<T> {
  let output!: T;
  try {
    await db.tx(async (transaction) => {
      output = await run(transaction);
      throw ROLLBACK;
    });
  } catch (error) {
    if (error !== ROLLBACK) throw error;
  }
  return output;
}

export function createFactory<T extends Record<string, unknown>>(build: (sequence: number) => T) {
  let sequence = 0;
  return {
    build(overrides: Partial<T> = {}): T {
      return { ...build(++sequence), ...overrides };
    },
    buildMany(count: number, overrides: Partial<T> = {}): T[] {
      if (!Number.isInteger(count) || count < 0 || count > 10_000)
        throw new RangeError("Factory count must be between 0 and 10000");
      return Array.from({ length: count }, () => ({ ...build(++sequence), ...overrides }));
    },
    reset(): void {
      sequence = 0;
    },
  };
}

export interface BrowserArtifactPage {
  screenshot(options: { path: string; fullPage?: boolean }): Promise<unknown>;
  context(): { tracing?: { stop(options: { path: string }): Promise<unknown> } };
}

export async function captureBrowserArtifacts(
  page: BrowserArtifactPage,
  testName: string,
  options: { root?: string; screenshot?: boolean; trace?: boolean } = {},
): Promise<{ screenshot?: string; trace?: string }> {
  const safe = testName.replace(/[^A-Za-z0-9_.-]+/g, "-").slice(0, 120) || "test";
  const root = resolve(options.root ?? join("test-results", "wrnexus"));
  mkdirSync(root, { recursive: true });
  const output: { screenshot?: string; trace?: string } = {};
  if (options.screenshot !== false) {
    output.screenshot = join(root, `${safe}.png`);
    await page.screenshot({ path: output.screenshot, fullPage: true });
  }
  if (options.trace && page.context().tracing) {
    output.trace = join(root, `${safe}.zip`);
    await page.context().tracing!.stop({ path: output.trace });
  }
  return output;
}
