import { defineConfig, devices } from '@playwright/test';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { mkdtempSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';

// __dirname under ESM.
const __dirname = dirname(fileURLToPath(import.meta.url));
// Repo root is one level up from ui/.
const repoRoot = resolve(__dirname, '..');

// Dedicated E2E port — avoids clashing with a dev server on 9876.
const PORT = Number(process.env.E2E_PORT ?? 8788);
const BASE_URL = `http://127.0.0.1:${PORT}`;

// Isolated, throwaway data + workspace dirs so the E2E never touches real data
// (real DB lives at ./data/maestro.db). A fresh temp DB also means the personal
// space is auto-created clean on first /api/local/spaces hit.
// The temp dir MUST be stable across the multiple times Playwright evaluates
// this config (main process + worker), otherwise mkdtempSync would mint a new
// dir per evaluation and the .e2e-db-path handoff file could point at a
// different DB than the one the webServer actually booted with (→ specs open an
// empty DB and fail with "no such table: jobs"). Self-seed it into the
// environment on the FIRST evaluation: subsequent evals in this process reuse
// it, and the webServer subprocess + worker processes inherit it via env. No
// caller setup required (plain `npm run test:e2e` works); an explicitly-set
// E2E_TMP is still honored.
if (!process.env.E2E_TMP) {
  process.env.E2E_TMP = mkdtempSync(join(tmpdir(), 'maestro-e2e-'));
}
const e2eTmp = process.env.E2E_TMP;
const DB_PATH = join(e2eTmp, 'e2e.db');
const WORKTREE_DIR = join(e2eTmp, 'workspaces');
// Expose the throwaway DB path to specs that need to seed engine state which
// can't be produced without a live LLM (e.g. a job parked for tool approval).
// Written to a gitignored file next to this config so specs can read it.
writeFileSync(join(__dirname, '.e2e-db-path'), DB_PATH);

export default defineConfig({
  testDir: resolve(__dirname, 'e2e'),
  // The build+boot can be slow; keep a roomy per-test budget.
  timeout: 60_000,
  expect: { timeout: 15_000 },
  fullyParallel: false,
  workers: 1,
  retries: 0,
  reporter: [['list']],

  use: {
    baseURL: BASE_URL,
    headless: true,
    // This sandbox disables the Chromium sandbox (bwrap/userns off). Launch
    // with --no-sandbox or every browser launch fails to spawn.
    launchOptions: {
      args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
    },
    trace: 'retain-on-failure',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],

  // Boot the real orchestrator (worker mode) with auth OFF (no config.yaml =>
  // defaults => synthetic 'local' user). dist/ must exist (npm run build:all).
  webServer: {
    command: 'node dist/main.js',
    cwd: repoRoot,
    url: `${BASE_URL}/ui`,
    reuseExistingServer: false,
    timeout: 120_000,
    stdout: 'pipe',
    stderr: 'pipe',
    env: {
      ...process.env,
      PORT: String(PORT),
      DB_PATH,
      WORKTREE_DIR,
      AAO_MODE: 'worker',
      LOG_LEVEL: 'warn',
      // Mount the MCP subsystem (gated on a 64-hex MCP_ENCRYPTION_KEY) so the
      // per-space MCP isolation E2E can register/list servers via /api/mcp/*.
      // A fixed throwaway key keeps the run deterministic; the temp DB it
      // encrypts is discarded after the run.
      MCP_ENCRYPTION_KEY: '00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff',
      // Configure a STUB LLM so the first-run SetupWizard never gates the UI
      // (it shows when no model is configured). The endpoint is never actually
      // reached by these specs — they assert UI/DB state, not model output — so
      // a fake host is fine and keeps the E2E self-contained (no caller env).
      OLLAMA_BASE_URL: process.env.OLLAMA_BASE_URL ?? 'http://127.0.0.1:11434/v1',
      OLLAMA_MODEL: process.env.OLLAMA_MODEL ?? 'e2e-stub-model',
    },
  },
});
