/**
 * @wrnexus/test — testing utilities for WrNexus apps. Runs on `bun test` (via
 * `wrnexus test`). Import everything from one place:
 *
 *   import { test, expect, renderComponent, mountHtml, createHarness } from "@wrnexus/test";
 *
 *   test("counter renders its label", async () => {
 *     const html = await renderComponent(SRC, { start: 3, label: "Hits" });
 *     expect(html).toContain("Hits");
 *   });
 *
 *   test("home page responds", async () => {
 *     const app = await createHarness("examples/basic-app");
 *     const res = await app.fetch("/");
 *     expect(res.status).toBe(200);
 *     await app.close();
 *   });
 */

import { mkdirSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
import { compileWireFile } from "@wrnexus/compiler";
import { createContext } from "@wrnexus/core";
import type { Context } from "@wrnexus/core";
import { getComponentControllerRuntime, getReactiveRuntime } from "@wrnexus/csr";
import { startServer } from "@wrnexus/dev-server";
import {
  loadAppConfig,
  loadEnv,
  findStyleEntry,
  headToString,
  resolveProfile,
} from "@wrnexus/styles";

// One-import DX: re-export the bun:test primitives.
export {
  test,
  expect,
  describe,
  it,
  beforeEach,
  afterEach,
  beforeAll,
  afterAll,
  mock,
  spyOn,
} from "bun:test";
export { createContext } from "@wrnexus/core";

// --- Component rendering ---------------------------------------------------

let seq = 0;

/** Compile a `.wrn` component source + render it to HTML with the given props. */
export async function renderComponent(
  source: string,
  props: Record<string, unknown> = {},
): Promise<string> {
  const dir = join(tmpdir(), "wrnexus-test");
  mkdirSync(dir, { recursive: true });
  const file = join(dir, `c${seq++}.ts`);
  writeFileSync(file, compileWireFile(source, file), "utf8");
  const mod = (await import(pathToFileURL(file).href)) as {
    render?: (props: Record<string, unknown>) => string;
  };
  if (typeof mod.render !== "function") {
    throw new Error("renderComponent expects a `component` (with a render export)");
  }
  return String(mod.render(props));
}

// --- Reactive DOM (happy-dom) ----------------------------------------------

/**
 * Mount server-rendered HTML in a happy-dom window with the reactive runtime
 * hydrated, so you can test `data-scope`/`data-text`/`data-for`/`data-show`
 * behaviour. Returns the window; assert on `win.document`.
 */
export function mountHtml(html: string): {
  document: Document;
  window: unknown;
  querySelector: (sel: string) => Element | null;
  querySelectorAll: (sel: string) => Element[];
} {
  // happy-dom is a dev dependency of the workspace; loaded lazily so importing
  // this package never requires it unless you actually mount DOM.
  // eslint-disable-next-line @typescript-eslint/no-require-imports
  const { Window } = require("happy-dom") as { Window: new () => unknown };
  const win = new Window() as Record<string, unknown> & { document: Document };
  (win.document as unknown as { body: { innerHTML: string } }).body.innerHTML =
    `<div id="app">${html}</div>`;
  const g = globalThis as Record<string, unknown>;
  g.window = win;
  g.document = win.document;
  g.NodeFilter = win.NodeFilter;
  g.HTMLInputElement = win.HTMLInputElement;
  g.HTMLOptionElement = win.HTMLOptionElement;
  g.HTMLSelectElement = win.HTMLSelectElement;
  g.HTMLTextAreaElement = win.HTMLTextAreaElement;
  (0, eval)(getReactiveRuntime());
  (0, eval)(getComponentControllerRuntime());
  (win as { __wrnexusHydrateScopes?: (root: unknown) => void }).__wrnexusHydrateScopes?.(
    win.document,
  );
  return {
    document: win.document,
    window: win,
    querySelector: (sel) => win.document.querySelector(sel),
    querySelectorAll: (sel) => Array.from(win.document.querySelectorAll(sel)),
  };
}

// --- API route handlers ----------------------------------------------------

/** Call an API route handler with a `Context` built from a Request. */
export async function callRoute(
  handler: (ctx: Context) => Response | Promise<Response>,
  request: Request,
): Promise<Response> {
  const ctx = createContext(request, new URL(request.url));
  return handler(ctx);
}

// --- Full app harness (real in-process server) -----------------------------

export interface Harness {
  /** Base URL of the ephemeral test server. */
  url: string;
  /** Fetch a path on the app (relative to `url`). */
  fetch(path: string, init?: RequestInit): Promise<Response>;
  /** The scanned router (pages/api/realtime/components). */
  router: unknown;
  /** Stop the server. */
  close(): void;
}

export interface HarnessOptions {
  /** Config/env profile. Default "test". */
  profile?: string;
}

/**
 * Boot the app on an ephemeral port for integration tests (pages, API routes,
 * middleware, the full pipeline). Uses the "test" profile by default so it picks
 * up your test database/env. Remember to `await app.close()`.
 */
export async function createHarness(
  projectRoot: string,
  options: HarnessOptions = {},
): Promise<Harness> {
  const root = resolve(projectRoot);
  const appDir = join(root, "app");
  const profile = resolveProfile({ explicit: options.profile ?? "test" });
  loadEnv(root, profile);
  const config = await loadAppConfig(root, profile);
  const styleEntry = findStyleEntry(appDir, root, config.styles?.entry);

  const server = await startServer({
    appDir,
    port: 0, // ephemeral
    mode: "development",
    hmr: false,
    styleEntry,
    stylesConfig: config.styles,
    head: headToString(config.head),
    seo: config.seo,
    security: config.security,
    theme: config.theme,
    i18n: config.i18n,
    db: config.db,
    databases: config.databases,
  });

  return {
    url: server.url,
    router: server.router,
    fetch: (path, init) => fetch(server.url + path, init),
    close: () => server.stop(),
  };
}

export {
  testRequest,
  testContext,
  readJsonResponse,
  expectProblem,
  deferred,
  waitFor,
  MemoryCookieJar,
} from "./advanced.ts";
export type { TestRequestOptions, JsonResponse, Deferred, WaitForOptions } from "./advanced.ts";
export { withDatabaseRollback, createFactory, captureBrowserArtifacts } from "./platform.ts";
export type { TransactionalDatabase, BrowserArtifactPage } from "./platform.ts";
