/* Minimal test runner — no test framework dep, just node assert.
 *
 * Usage:
 *   node test/run.js            — runs all test/*.test.js files
 *   node test/run.js auth       — runs test/auth.test.js
 *
 * Each test file is a module that exports an array of
 *   { name: string, fn: () => Promise<void> | void }
 * or a default export of one such array.
 *
 * Tests are isolated: each test file gets a fresh in-memory sqlite
 * via `setupFreshDb()` so they can't poison each other.
 */

'use strict';

const path = require('path');
const fs = require('fs');
const assert = require('assert');

const COLOR = { red: '\x1b[31m', green: '\x1b[32m', dim: '\x1b[2m', reset: '\x1b[0m' };

async function runOne(file) {
  const fullPath = path.resolve(__dirname, file);
  if (!fs.existsSync(fullPath)) {
    console.error(`${COLOR.red}✗ ${file} not found${COLOR.reset}`);
    return { pass: 0, fail: 1, errors: [`${file} not found`] };
  }
  console.log(`\n${COLOR.dim}━━ ${file} ━━${COLOR.reset}`);
  process.env.NCII_TEST = '1';
  // Reset require cache for this file
  delete require.cache[fullPath];
  const mod = require(fullPath);
  const tests = Array.isArray(mod) ? mod : (mod.tests || (mod.default && mod.default.tests) || []);
  // If the test file declares `requiresServer: true`, the runner
  // checks that the live server is reachable and SKIPS with a notice
  // if not (rather than failing).
  //
  // Two declaration styles are supported:
  //   - module-level:  module.exports.requiresServer = true;
  //   - per-test:      test(name, fn, { requiresServer: true });
  //                    or:  TESTS.push({ name, fn, requiresServer: true });
  const requiresServer = mod.requiresServer
    || (mod.default && mod.default.requiresServer)
    || tests.some(t => t.requiresServer);
  if (requiresServer) {
    const reachable = await isServerUp();
    if (!reachable) {
      console.log(`  ${COLOR.dim}⊘ skipped (server not running on :3000; start app.js to run these)${COLOR.reset}`);
      return { pass: 0, fail: 0, errors: [], skipped: tests.length };
    }
  }
  let pass = 0, fail = 0;
  for (const t of tests) {
    try {
      await t.fn();
      console.log(`  ${COLOR.green}✓${COLOR.reset} ${t.name}`);
      pass++;
    } catch (e) {
      console.log(`  ${COLOR.red}✗ ${t.name}${COLOR.reset}`);
      console.log(`    ${COLOR.red}${e.message}${COLOR.reset}`);
      if (process.env.VERBOSE) console.log(e.stack);
      fail++;
    }
  }
  return { pass, fail, errors: [] };
}

function isServerUp() {
  return new Promise((resolve) => {
    const r = require('http').request({hostname:'127.0.0.1', port:3000, path:'/health/live', method:'GET', timeout: 1000}, (res) => {
      resolve(res.statusCode === 200);
    });
    r.on('error', () => resolve(false));
    r.on('timeout', () => { r.destroy(); resolve(false); });
    r.end();
  });
}

async function main() {
  const filter = process.argv[2];
  const files = fs.readdirSync(__dirname)
    .filter(f => f.endsWith('.test.js') && (!filter || f.includes(filter)));
  if (!files.length) {
    console.error(`No test files found${filter ? ` matching "${filter}"` : ''}`);
    process.exit(1);
  }
  let totalPass = 0, totalFail = 0, totalSkip = 0;
  for (const f of files) {
    const r = await runOne(f);
    totalPass += r.pass;
    totalFail += r.fail;
    totalSkip += r.skipped || 0;
  }
  console.log(`\n${COLOR.dim}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLOR.reset}`);
  let summary = `  ${totalPass} passed · ${COLOR.red}${totalFail} failed${COLOR.reset}`;
  if (totalSkip) summary += ` · ${COLOR.dim}${totalSkip} skipped${COLOR.reset}`;
  console.log(summary);
  process.exit(totalFail ? 1 : 0);
}

main().catch(e => { console.error(e); process.exit(1); });
