#!/usr/bin/env node
/**
 * Smoke test for ruvnet/ruflo#2132 — Windows-compatible init hook generation.
 *
 * Verifies that `ruflo init` generates a .claude/settings.json containing
 * node-based hook commands (no /bin/bash, no POSIX pipelines), and that the
 * platform detection correctly distinguishes Windows from POSIX.
 *
 * Runs on: ubuntu-latest, macos-latest, windows-latest (CI matrix)
 *
 * On windows-latest: asserts settings.json has node-based hooks
 * On ubuntu/macos: asserts settings.json has POSIX-compatible hooks and that
 *   no Windows-specific cmd.exe / %USERPROFILE% patterns appear
 */

import { spawnSync } from 'node:child_process';
import { mkdtempSync, readFileSync, existsSync } from 'node:fs';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { dirname, resolve } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const REPO_ROOT = resolve(__dirname, '..');
const CLI_BIN = join(REPO_ROOT, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js');

const IS_WINDOWS = process.platform === 'win32';

let passed = 0;
let failed = 0;

function assert(condition, message) {
  if (condition) {
    console.log(`  pass: ${message}`);
    passed++;
  } else {
    console.error(`  FAIL: ${message}`);
    failed++;
  }
}

function assertNot(condition, message) {
  assert(!condition, message);
}

// Run ruflo init in a temporary directory
const tmpDir = mkdtempSync(join(tmpdir(), 'ruflo-smoke-win-init-'));
console.log(`Running ruflo init in: ${tmpDir}`);

const result = spawnSync(
  process.execPath,
  [CLI_BIN, 'init', '--yes', '--skip-prompts', '--no-install'],
  {
    cwd: tmpDir,
    env: {
      ...process.env,
      // Ensure non-interactive mode regardless of TTY
      CI: 'true',
      FORCE_COLOR: '0',
    },
    encoding: 'utf8',
    // iter 123 → 131 — bumped from 180s to 300s. macos-latest observed
    // running exactly 180s before the timer fired (CI cold ONNX download
    // + agentic-flow init + MCP server spawn). 300s leaves room for the
    // smoke's assertion phase under the job's 10-min cap.
    timeout: 300_000,
    // Don't fail if the command exits non-zero — init may partially succeed
  }
);

console.log('init exit code:', result.status);
if (result.stderr && result.stderr.trim()) {
  console.log('init stderr (first 500 chars):', result.stderr.slice(0, 500));
}

const settingsPath = join(tmpDir, '.claude', 'settings.json');

if (!existsSync(settingsPath)) {
  // Try alternate location (some init modes write to cwd/.claude/)
  console.log('settings.json not found, listing tmp dir:');
  try {
    const { readdirSync } = await import('node:fs');
    const entries = readdirSync(tmpDir, { recursive: true });
    entries.slice(0, 20).forEach(e => console.log(' ', e));
  } catch { /* ignore */ }
  console.error('FAIL: settings.json not generated by ruflo init');
  process.exit(1);
}

const settingsText = readFileSync(settingsPath, 'utf8');
let settings;
try {
  settings = JSON.parse(settingsText);
} catch (e) {
  console.error('FAIL: settings.json is not valid JSON:', e.message);
  process.exit(1);
}

console.log(`\nPlatform: ${process.platform}`);
console.log('Verifying settings.json hook commands...\n');

// Collect all hook commands for inspection
const allCommands = [];
function collectCommands(obj, path) {
  if (!obj || typeof obj !== 'object') return;
  if (Array.isArray(obj)) {
    obj.forEach((item, i) => collectCommands(item, `${path}[${i}]`));
    return;
  }
  if (typeof obj.command === 'string') {
    allCommands.push({ command: obj.command, path });
  }
  for (const [k, v] of Object.entries(obj)) {
    collectCommands(v, `${path}.${k}`);
  }
}

if (settings.hooks) {
  collectCommands(settings.hooks, 'hooks');
}

console.log(`Found ${allCommands.length} hook command(s) in settings.json`);
allCommands.slice(0, 5).forEach(({ command, path }) => {
  console.log(`  [${path}]: ${command.slice(0, 80)}...`);
});

if (IS_WINDOWS) {
  console.log('\n--- Windows assertions ---');
  // On Windows: all hook commands must be node-based (no /bin/bash, no | jq)
  assert(allCommands.length > 0, 'settings.json has at least one hook command');

  for (const { command, path } of allCommands) {
    assertNot(
      /\/bin\/bash\b/.test(command),
      `[${path}] has no /bin/bash literal`
    );
    assertNot(
      /\/bin\/sh\b/.test(command),
      `[${path}] has no /bin/sh literal`
    );
    assertNot(
      /\|\s*jq\b/.test(command),
      `[${path}] has no pipe-to-jq`
    );
    assertNot(
      /\.sh\b/.test(command),
      `[${path}] has no .sh script reference`
    );
  }

  // Check that hook-handler.cjs was deployed
  const handlerPath = join(tmpDir, '.claude', 'helpers', 'hook-handler.cjs');
  assert(existsSync(handlerPath), '.claude/helpers/hook-handler.cjs exists');

  // Check that ruflo-hook.cjs was deployed (new in #2132)
  const shimPath = join(tmpDir, '.claude', 'helpers', 'ruflo-hook.cjs');
  assert(existsSync(shimPath), '.claude/helpers/ruflo-hook.cjs exists (#2132)');

} else {
  console.log('\n--- POSIX (Mac/Linux) assertions ---');
  // On POSIX: settings.json may use sh/node hybrid; must NOT have Windows cmd.exe patterns
  assert(allCommands.length > 0, 'settings.json has at least one hook command');

  for (const { command, path } of allCommands) {
    assertNot(
      /cmd\s+\/c\b/.test(command) && /%USERPROFILE%/.test(command),
      `[${path}] has no Windows-only cmd.exe + %USERPROFILE% pattern`
    );
  }

  // hook-handler.cjs must still be deployed on POSIX
  const handlerPath = join(tmpDir, '.claude', 'helpers', 'hook-handler.cjs');
  assert(existsSync(handlerPath), '.claude/helpers/hook-handler.cjs exists');

  // ruflo-hook.cjs is always deployed now (cross-platform shim always available)
  const shimPath = join(tmpDir, '.claude', 'helpers', 'ruflo-hook.cjs');
  assert(existsSync(shimPath), '.claude/helpers/ruflo-hook.cjs exists (#2132)');
}

console.log(`\nResults: ${passed} passed, ${failed} failed`);
if (failed > 0) {
  process.exit(1);
}
console.log('ok: smoke-windows-init-hooks passed');
