// vitest globalSetup: runs once in the main process, before any worker starts,
// and its returned teardown runs once after every worker has finished.
//
// Why this exists: createTestDb() used to CREATE DATABASE + replay all
// migrations + seed, once per test file. At 30-odd files that is 30 full
// schema builds per run, and the three files that build their database inside
// an `it()` body (companies/db/health) would blow vitest's default 5s test
// timeout whenever the machine was busy. Here we pay for the schema exactly
// once, into a template database; createTestDb() then copies it.

import type { TestProject } from "vitest/node";
import {
  buildTemplateDatabase,
  dropTestDatabase,
  reportLeakedDatabases,
  resolveAdminUrl,
  sweepStaleTestDatabases,
} from "./test-db-admin.js";

declare module "vitest" {
  export interface ProvidedContext {
    /** Name of this run's migrated+seeded template database. */
    templateDb: string;
  }
}

export default async function setup(project: TestProject) {
  // Say which database this run is about to create and destroy databases on,
  // before touching it. Since Docker was removed there are two real answers —
  // a PostgreSQL the developer configured, or the one Balanced Sum installed on
  // this machine — and "the tests passed against the wrong server" is a silent
  // failure that costs an afternoon. The URL is printed REDACTED: on the
  // bundled path it carries the generated superuser password, which is allowed
  // to exist in its 0600 file and nowhere else.
  const admin = await resolveAdminUrl();
  console.log(
    admin.source === "bundled"
      ? `[test-db] using the database Balanced Sum installed on this computer: ${admin.redacted}`
      : `[test-db] using the database your ${admin.via} points at: ${admin.redacted}`,
  );

  // Crashed runs leave throwaway databases behind; they eat disk and
  // connection slots until someone notices. Self-heal anything untouched for
  // an hour. Only names matching the generated test-database patterns are
  // even considered (see isDisposableTestDb).
  const swept = await sweepStaleTestDatabases();
  if (swept.length > 0) {
    console.log(`[test-db] swept ${swept.length} stale test database(s): ${swept.join(", ")}`);
  }

  const templateDb = await buildTemplateDatabase();
  project.provide("templateDb", templateDb);
  // Belt and braces: workers are forked after globalSetup, so they inherit
  // this. Keeps the helper working even if the provide/inject channel is
  // unavailable (e.g. a test file imported outside a vitest project context).
  process.env.ACCOUNTING_TEST_TEMPLATE_DB = templateDb;
  console.log(`[test-db] template ready: ${templateDb}`);

  return async () => {
    // Runs after every worker has finished. Any of THIS run's throwaway
    // databases still standing belongs to a file whose worker died before its
    // afterAll — the failure vitest reports as "Worker exited unexpectedly",
    // with a test total that is short by an unexplained amount and no file name
    // anywhere in the output. The database carries the file name; say it.
    //
    // Printed as an error, not a warning, and deliberately verbose. Losing a
    // file's results is not a footnote in a project whose test suite is the
    // proof that journal entries balance and that one company cannot read
    // another's books (CLAUDE.md §11).
    const leaked = await reportLeakedDatabases(templateDb);
    if (leaked.length > 0) {
      console.error(
        `\n[test-db] ${leaked.length} test database(s) from this run were never cleaned up. That means the ` +
        "worker running each of these files exited before its afterAll — its results are MISSING from the " +
        "totals above, even if nothing was reported as failed:\n" +
        leaked.map((l) => `  - ${l.file ?? "(file unknown)"}  [${l.database}]`).join("\n") +
        "\nRe-run those files on their own to see what they do.\n",
      );
      // And FAIL THE RUN, which printing alone never did.
      //
      // This message used to be the whole response, and a message is not a
      // gate. `npm test` still returned 0 whenever vitest's own accounting
      // happened not to notice the death, so a run that had quietly dropped a
      // file's worth of invariant tests was indistinguishable, to anything
      // reading the exit code, from a clean one. CI, a pre-push hook and a
      // person skimming for red are all "anything reading the exit code".
      //
      // process.exitCode rather than throwing: vitest runs globalSetup
      // teardown inside Vitest.close(), and an error thrown there is caught
      // and logged as "error during close" WITHOUT touching the exit code
      // (cli-api's close(): teardownErrors are collected and printed, nothing
      // more). It then ends with a bare `process.exit()`, which honours
      // whatever process.exitCode says — so setting it is the one thing here
      // that actually reaches the shell.
      //
      // tests/helpers/completeness-reporter.ts fails the same runs from the
      // other side, by comparing scheduled files against reported ones. Two
      // detectors on purpose: that one sees every file but only knows a name,
      // this one only sees files that got as far as creating a database but
      // hands you the database itself, stamped with its owner, to look inside.
      process.exitCode = 1;
    }

    await dropTestDatabase(templateDb).catch((err: unknown) => {
      console.warn(`[test-db] could not drop template ${templateDb}: ${String(err)}`);
    });
  };
}
