import { randomBytes } from "node:crypto";
import path from "node:path";
import { defineConfig, devices } from "@playwright/test";

const authFile = path.join(__dirname, ".playwright", ".auth", "user.json");

const TEST_WEB_PORT = 2349;

// Fresh Postgres database per e2e run.  The create-db script (chained before
// the API in the webServer command) CREATEs it and cleans up stale databases
// from previous runs.  The API auto-migrates the empty database at boot.
const E2E_PG_BASE_URL =
  process.env.E2E_PG_BASE_URL || "postgres://snapotter:snapotter@localhost:5432/snapotter";
const e2eDbName = `snapotter_e2e_${process.pid}_${randomBytes(4).toString("hex")}`;
const e2eDatabaseUrl = (() => {
  const url = new URL(E2E_PG_BASE_URL);
  url.pathname = `/${e2eDbName}`;
  return url.toString();
})();

// Specs that mutate global server state (settings, users, roles, API keys)
// or assert on global lists/timing. These run in the chromium-serial project
// with --workers=1; everything else parallelizes safely.
const SERIAL_SPECS =
  /gui-settings-|settings\.spec|rbac|security|people|api\.spec|state-bleed|full-session|gui-file-carry|i18n|theme|gui-performance/;

// Screenshot-comparison specs. Separate project because baselines are
// platform-specific: they run locally (darwin baselines) and via the
// update-visual-baselines workflow, but not in the nightly linux run until
// linux baselines are committed.
const VISUAL_SPECS = /visual-regression|gui-visual-/;

// Device-emulated specs (real touch, UA, DPR). Tagged @mobile or @tablet
// and routed to the dedicated device projects below.
const DEVICE_SPECS = /device-mobile|device-tablet|device-visual|device-a11y/;

export default defineConfig({
  testDir: "./tests/e2e",
  timeout: 30_000,
  expect: {
    timeout: 10_000,
    toHaveScreenshot: {
      maxDiffPixelRatio: 0.01,
      animations: "disabled",
      caret: "hide",
    },
  },
  // Platform-suffixed baselines: darwin baselines serve local runs on macOS,
  // linux baselines (generated by the update-visual-baselines workflow) serve CI.
  snapshotPathTemplate: "{testDir}/__screenshots__/{testFilePath}/{arg}-{platform}{ext}",
  fullyParallel: false,
  retries: process.env.CI ? 1 : 0,
  // Files run across workers; tests within a file stay ordered. The serial
  // bucket is pinned to --workers=1 by its run command. Default is 2: the
  // dev-mode webServers saturate beyond that and 30s-timeout tests start
  // flaking. Raise via PW_WORKERS on stronger setups.
  workers: process.env.PW_WORKERS ? Number(process.env.PW_WORKERS) : 2,
  reporter: "html",
  use: {
    baseURL: `http://localhost:${TEST_WEB_PORT}`,
    screenshot: "only-on-failure",
    trace: "retain-on-failure",
  },
  projects: [
    {
      name: "setup",
      testMatch: /auth\.setup\.ts/,
    },
    {
      name: "chromium",
      use: {
        ...devices["Desktop Chrome"],
        storageState: authFile,
      },
      testIgnore: [SERIAL_SPECS, VISUAL_SPECS, DEVICE_SPECS],
      dependencies: ["setup"],
    },
    {
      name: "chromium-serial",
      use: {
        ...devices["Desktop Chrome"],
        storageState: authFile,
      },
      testMatch: SERIAL_SPECS,
      dependencies: ["setup"],
    },
    {
      name: "chromium-visual",
      use: {
        ...devices["Desktop Chrome"],
        storageState: authFile,
      },
      testMatch: VISUAL_SPECS,
      dependencies: ["setup"],
    },
    {
      name: "firefox",
      use: {
        ...devices["Desktop Firefox"],
        storageState: authFile,
      },
      testMatch: /gui-cross-browser\.spec\.ts/,
      dependencies: ["setup"],
    },
    {
      name: "webkit",
      use: {
        ...devices["Desktop Safari"],
        storageState: authFile,
      },
      testMatch: /gui-cross-browser\.spec\.ts/,
      dependencies: ["setup"],
    },

    // ---- Real device-emulated projects (touch, UA, DPR, engine) ----
    {
      name: "mobile-chromium",
      use: {
        ...devices["Pixel 7"],
        storageState: authFile,
      },
      testMatch: DEVICE_SPECS,
      grep: /@mobile/,
      dependencies: ["setup"],
    },
    {
      name: "mobile-webkit",
      use: {
        ...devices["iPhone 14"],
        storageState: authFile,
      },
      testMatch: DEVICE_SPECS,
      grep: /@mobile/,
      dependencies: ["setup"],
    },
    {
      name: "tablet-webkit",
      use: {
        ...devices["iPad (gen 7)"],
        storageState: authFile,
      },
      testMatch: DEVICE_SPECS,
      grep: /@tablet/,
      dependencies: ["setup"],
    },
    {
      name: "tablet-chromium",
      use: {
        ...devices["Galaxy Tab S9"],
        storageState: authFile,
      },
      testMatch: DEVICE_SPECS,
      grep: /@tablet/,
      dependencies: ["setup"],
    },
  ],
  webServer: [
    {
      command: `node tests/e2e-pg-create-db.cjs ${e2eDbName} && pnpm --filter @snapotter/api dev`,
      port: 13490,
      reuseExistingServer: !process.env.CI,
      env: {
        AUTH_ENABLED: "true",
        DEFAULT_USERNAME: "admin",
        DEFAULT_PASSWORD: "admin",
        RATE_LIMIT_PER_MIN: "50000",
        SKIP_MUST_CHANGE_PASSWORD: "true",
        ANALYTICS_ENABLED: "false",
        DATABASE_URL: e2eDatabaseUrl,
        REDIS_URL: process.env.REDIS_URL ?? "redis://localhost:6379",
        BULLMQ_PREFIX: e2eDbName,
        // The in-repo docker/feature-manifest.json makes the API think it is
        // inside Docker and try to mkdir /data; point it somewhere writable.
        DATA_DIR: path.join(__dirname, "test-results", ".e2e-data"),
      },
      timeout: 30_000,
    },
    {
      // Production build + static preview: the dev server's on-demand
      // transform saturates under parallel workers and flakes 30s-timeout
      // tests. The build adds ~40s once per run and removes that whole class.
      command: "pnpm --filter @snapotter/web build && pnpm --filter @snapotter/web preview",
      port: TEST_WEB_PORT,
      reuseExistingServer: !process.env.CI,
      env: {
        PORT: String(TEST_WEB_PORT),
        VITE_API_URL: "http://localhost:13490",
      },
      timeout: 240_000,
    },
  ],
});

export { authFile };
