/**
 * Vitest global setup. Runs once per test file before any test bodies.
 *
 * Responsibilities:
 *   1. Load .env.test (falls back to env vars already in process.env if missing).
 *   2. Override DATABASE_URL / VALKEY_URL with the *test* equivalents BEFORE any
 *      backend service modules are imported — services capture `getDb()` at module
 *      load, so this rewrite must happen first.
 *   3. Run the migration against the test schema (idempotent — single migrate.js).
 *   4. Provide a `beforeEach` truncate hook for any test that imports the helper.
 *
 * Why this file is small: helpers live under test/helpers/* and are imported lazily
 * by individual test files. This file is intentionally just env setup + migrate.
 */

import { existsSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { config as loadDotenv } from 'dotenv'
import { spawnSync } from 'node:child_process'
import { afterAll, beforeAll } from 'vitest'

const __dirname = dirname(fileURLToPath(import.meta.url))
const backendRoot = resolve(__dirname, '..')

// 1. Load .env.test if it exists
const envTestPath = resolve(backendRoot, '.env.test')
if (existsSync(envTestPath)) {
  loadDotenv({ path: envTestPath })
}

// 2. Validate required env (fail fast — silent fallback to dev DB would be catastrophic)
const required = ['TEST_DATABASE_URL', 'TEST_VALKEY_URL', 'AUTH_JWT_SECRET']
for (const key of required) {
  if (!process.env[key]) {
    throw new Error(
      `Missing required test env var: ${key}. Copy .env.test.example → .env.test or set it in your shell.`
    )
  }
}
if (process.env.AUTH_JWT_SECRET.length < 32) {
  throw new Error('AUTH_JWT_SECRET must be at least 32 chars')
}

const TEST_SCHEMA = process.env.TEST_DB_SCHEMA || 'halobestie_test'
if (TEST_SCHEMA === 'public') {
  // Hard guard: schema isolation only works if we don't reuse the dev `public` schema.
  throw new Error(
    `TEST_DB_SCHEMA must not be "public" (would clobber dev tables). ` +
    `Set TEST_DB_SCHEMA to something like "halobestie_test".`
  )
}

// 3. Build the schema-scoped URL used by getDb() in services.
//    The `?options=-c search_path=...` query param tells Postgres to set search_path
//    on every new connection. All CREATE TABLE / INSERT / SELECT then default to the
//    test schema, leaving the dev `public` schema untouched.
const baseTestUrl = process.env.TEST_DATABASE_URL
const sep = baseTestUrl.includes('?') ? '&' : '?'
const scopedTestUrl = `${baseTestUrl}${sep}options=${encodeURIComponent(`-c search_path=${TEST_SCHEMA},public`)}`

// CRITICAL: rewrite the env vars services read at module load time. Must happen before
// any `import { ... } from '../src/services/...'` in a test or helper.
process.env.DATABASE_URL = scopedTestUrl
process.env.VALKEY_URL = process.env.TEST_VALKEY_URL

beforeAll(async () => {
  // Ensure the schema exists. Use a one-shot connection that's NOT the singleton.
  const { default: postgres } = await import('postgres')
  const bootstrap = postgres(process.env.TEST_DATABASE_URL)
  try {
    await bootstrap`CREATE SCHEMA IF NOT EXISTS ${bootstrap(TEST_SCHEMA)}`
  } finally {
    await bootstrap.end()
  }

  // Run the migration via a child process so we don't conflict with the singleton
  // sql client this test process will use. migrate.js calls sql.end() at the bottom,
  // which would tear down the shared client if invoked in-process.
  const result = spawnSync(
    process.execPath,
    [resolve(backendRoot, 'src/db/migrate.js')],
    {
      env: {
        ...process.env,
        DATABASE_URL: scopedTestUrl,
      },
      cwd: backendRoot,
      encoding: 'utf8',
    }
  )
  if (result.status !== 0) {
    throw new Error(
      `Test migration failed (exit ${result.status}):\n` +
      `stdout: ${result.stdout}\nstderr: ${result.stderr}`
    )
  }
}, 60_000)

afterAll(async () => {
  // Best-effort cleanup of any singletons opened by the test process. Each helper that
  // opens a connection registers its own teardown; this is a safety net.
  try {
    const { getDb } = await import('../src/db/client.js')
    const sql = getDb()
    await sql.end({ timeout: 5 })
  } catch {
    // Singleton was never created — fine.
  }
})
