#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const TEMPLATE_DIR =   __dirname;
const YES = process.argv.includes('--yes') || process.argv.includes('-y');
const IS_UPDATE = process.argv.includes('--update') || process.argv.includes('-u');
const IS_SELF = process.argv.includes('--self') || process.argv.includes('-s');

// Files that belong to the user — NEVER overwritten
const USER_FILES = new Set([
  '.qa-workflow.json',
  'opencode.json',
  'docs/application-context.md',
]);

// Directories that are pure user data — NEVER touched
const USER_DIRS = new Set([
  'user-story',
  'specs',
  'tests',
  'test-results',
  '.qa-context',
  '.auth',
]);

// Template files to copy/update
const QA_ITEMS = [
  { src: 'ai-qa-workflow.js', dest: 'ai-qa-workflow.js' },
  { src: 'scripts', dest: 'scripts', dir: true },
  { src: '.qa-workflow.json', dest: '.qa-workflow.json' },
  { src: 'prompts', dest: 'prompts', dir: true },
  { src: '.github/agents', dest: '.github/agents', dir: true },
  { src: '.opencode', dest: '.opencode', dir: true },
   { src: 'README.md', dest: 'README.md' },
  { src: 'PROJECT_GUIDE.md', dest: 'PROJECT_GUIDE.md' },
    { src: 'prompting_template.md', dest: 'prompting_template.md' },
    { src: '.cursorrules', dest: '.cursorrules' },
     { src: '.geminirules', dest: '.geminirules' },
     { src: '.github/copilot-instructions.md', dest: '.github/copilot-instructions.md' },
      { src: 'router.md', dest: 'router.md' },
  { src: '.opencode.json', dest: '.opencode.json' },

    ];

// Files to copy even during update (excludes user config files)
const UPDATE_ITEMS = QA_ITEMS.filter(item => {
  if (item.dir) return !USER_DIRS.has(item.dest);
  return !USER_FILES.has(item.dest);
});

const DIRS_TO_CREATE = ['user-story', 'specs', 'tests', 'test-results', '.qa-context', '.auth', 'templates'];

const NPM_SCRIPTS = {
  'qa': 'node ai-qa-workflow.js',
  'qa:init': 'node ai-qa-workflow.js init',
  'qa:plan': 'node ai-qa-workflow.js plan',
  'qa:generate': 'node ai-qa-workflow.js generate',
  'qa:execute': 'node ai-qa-workflow.js execute',
  'qa:heal': 'node ai-qa-workflow.js heal',
  'qa:retry': 'node ai-qa-workflow.js heal',
  'qa:report': 'node ai-qa-workflow.js report',
  'qa:report:allure': 'node ai-qa-workflow.js report:allure',
  'qa:status': 'node ai-qa-workflow.js status',
  'qa:list': 'node ai-qa-workflow.js list',
  'dashboard': 'cd qa-dashboard && npm start',
  'dashboard:dev': 'cd qa-dashboard && npx nodemon app.js',
  'dashboard:stop': 'npx kill-port 4000',
};

const BANNER = `
  ╔══════════════════════════════════════════╗
  ║    AI QA Pipeline Installer v2.0         ║
  ╚══════════════════════════════════════════╝
`;

function copyRecursive(src, dest, skipDirs) {
  skipDirs = skipDirs || new Set();
  const stat = fs.statSync(src);
  if (stat.isDirectory()) {
    const base = path.basename(src);
    if (skipDirs.has(base)) return;
    if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
    fs.readdirSync(src).forEach(item => copyRecursive(path.join(src, item), path.join(dest, item), skipDirs));
  } else {
    if (!fs.existsSync(dest) || fs.readFileSync(src).length !== fs.readFileSync(dest).length) {
      fs.copyFileSync(src, dest);
      return true;
    }
  }
  return false;
}

function countFiles(dir) {
  let count = 0;
  if (!fs.existsSync(dir)) return 0;
  fs.readdirSync(dir).forEach(item => {
    try {
      const full = path.join(dir, item);
      if (fs.statSync(full).isDirectory()) count += countFiles(full);
      else count++;
    } catch (e) {}
  });
  return count;
}

function addNpmScripts(pkgPath, overwrite) {
  if (!fs.existsSync(pkgPath)) return;
  let pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
  if (!pkg.scripts) pkg.scripts = {};
  let changed = 0;
  for (const [key, val] of Object.entries(NPM_SCRIPTS)) {
    if (!pkg.scripts[key] || overwrite) {
      pkg.scripts[key] = val;
      changed++;
    }
  }
  fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
  return changed;
}

function ask(query) {
  const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
  return new Promise(resolve => rl.question(query, a => { rl.close(); resolve(a.toLowerCase()); }));
}

async function install(targetPath, mode) {
  const isUpdate = mode === 'update';

  console.log(BANNER);
  console.log(`  Mode: ${isUpdate ? 'UPDATE (preserves config)' : 'FRESH INSTALL'}`);
  console.log(`  Target: ${targetPath}\n`);

  // 1. Copy template files
  const items = isUpdate ? UPDATE_ITEMS : QA_ITEMS;
  console.log(`  ── Step 1: QA Pipeline Files ──`);
  for (const item of items) {
    const srcPath = path.join(TEMPLATE_DIR, item.src);
    const destPath = path.join(targetPath, item.dest);
    if (!fs.existsSync(srcPath)) { console.log(`  ⚠  ${item.src} not found, skipping`); continue; }
    if (item.dir) {
      if (!fs.existsSync(destPath)) fs.mkdirSync(destPath, { recursive: true });
      const skipDirs = isUpdate ? new Set(['node_modules', 'data']) : new Set();
      copyRecursive(srcPath, destPath, skipDirs);
      const count = countFiles(srcPath);
      console.log(`  ✓ ${item.dest}/ (${count} files)`);
    } else {
      fs.copyFileSync(srcPath, destPath);
      console.log(`  ✓ ${item.dest}`);
    }
  }

  // 2. Create directories (fresh install only)
  if (!isUpdate) {
    console.log(`\n  ── Step 2: Project Directories ──`);
    for (const dir of DIRS_TO_CREATE) {
      const dirPath = path.join(targetPath, dir);
      if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); console.log(`  ✓ ${dir}/`); }
      else console.log(`  • ${dir}/ (exists)`);
    }
  }

  // 3. Add/update npm scripts
  console.log(`\n  ── Step 3: NPM Scripts ──`);
  const pkgPath = path.join(targetPath, 'package.json');
  const changed = addNpmScripts(pkgPath, isUpdate);
  if (changed > 0) console.log(`  ✓ ${isUpdate ? 'Updated' : 'Added'} ${changed} npm scripts (qa:*, dashboard)`);
  else console.log(`  • Scripts already configured`);

  // 4. Dashboard
  const dashboardSrc = path.join(TEMPLATE_DIR, 'qa-dashboard');
  const dashboardDest = path.join(targetPath, 'qa-dashboard');
  if (fs.existsSync(dashboardSrc)) {
    console.log(`\n  ── Step 4: QA Dashboard ──`);
    if (!fs.existsSync(dashboardDest)) {
      fs.mkdirSync(dashboardDest, { recursive: true });
      copyRecursive(dashboardSrc, dashboardDest);
      console.log(`  ✓ Dashboard copied to qa-dashboard/`);
      console.log(`  → Installing dashboard dependencies...`);
      try { execSync('npm install', { cwd: dashboardDest, stdio: 'pipe', timeout: 120000 }); console.log(`  ✓ Dashboard deps installed`); } catch (e) { console.log(`  ⚠  cd qa-dashboard && npm install`); }
    } else {
      const skipDirs = new Set(['node_modules', 'data']);
      copyRecursive(dashboardSrc, dashboardDest, skipDirs);
      console.log(`  ✓ Dashboard updated (preserved data/, node_modules/)`);
      if (!isUpdate) {
        console.log(`  → Installing dashboard dependencies...`);
        try { execSync('npm install', { cwd: dashboardDest, stdio: 'pipe', timeout: 120000 }); console.log(`  ✓ Dashboard deps installed`); } catch (e) { console.log(`  ⚠  cd qa-dashboard && npm install`); }
      }
    }
  }

  // 5. Register project in dashboard (fresh install only)
  if (!isUpdate && fs.existsSync(dashboardDest)) {
    console.log(`\n  ── Step 5: Link Dashboard → Project ──`);
    const dashDataDir = path.join(dashboardDest, 'data');
    const dashProjectsFile = path.join(dashDataDir, 'projects.json');
    if (fs.existsSync(dashDataDir)) {
      let projects = [];
      if (fs.existsSync(dashProjectsFile)) {
        try { projects = JSON.parse(fs.readFileSync(dashProjectsFile, 'utf-8')); } catch(e) {}
      }
      const projectName = path.basename(targetPath);
      const projectId = projectName.toLowerCase().replace(/[^a-z0-9]+/g, '-');
      if (!projects.find(p => p.id === projectId)) {
        projects.push({ id: projectId, name: projectName, path: targetPath, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() });
        fs.writeFileSync(dashProjectsFile, JSON.stringify(projects, null, 2));
        console.log(`  ✓ Project "${projectName}" registered in dashboard`);
      }
    }
  }

  // 6. Install Playwright (fresh only)
  if (!isUpdate) {
    console.log(`\n  ── Step 6: Dependencies ──`);
    if (!fs.existsSync(path.join(targetPath, 'node_modules', '@playwright'))) {
      console.log(`  → Installing @playwright/test...`);
      try { execSync('npm install @playwright/test', { cwd: targetPath, stdio: 'pipe', timeout: 120000 }); console.log(`  ✓ Playwright installed`); } catch (e) { console.log(`  ⚠  npm install failed: npm install @playwright/test`); }
    } else console.log(`  • Playwright already installed`);
  }

  // Summary
  const totalFiles = countFiles(path.join(targetPath, 'scripts'));
  const dashboardCount = countFiles(path.join(targetPath, 'qa-dashboard'));

  console.log(`\n  ╔══════════════════════════════════════════╗`);
  console.log(`  ║   ✓ ${isUpdate ? 'UPDATE COMPLETE' : 'INSTALLATION COMPLETE'}${' '.repeat(isUpdate ? 13 : 7)}║`);
  console.log(`  ╚══════════════════════════════════════════╝\n`);

  if (isUpdate) {
    console.log(`  Pipeline files updated. Your config and data are preserved.`);
    console.log(`  Restart the dashboard if it was running.\n`);
  } else {
    console.log(`  Files: ~${totalFiles} scripts + ${dashboardCount} dashboard files`);
    console.log(`  Next:\n`);
    console.log(`  npm run qa:init      Initialize pipeline (config + dirs + auth)`);
    console.log(`  npm run qa:execute   Run Playwright tests`);
    console.log(`  npm run qa:status    Check pipeline state`);
    console.log(`  npm run dashboard    Start dashboard (port 4000)\n`);
  }
}

// ---- CLI entry ----
const args = process.argv.slice(2);
const isHelp = !args.length || args.includes('--help') || args.includes('-h');

if (isHelp) {
  console.log(`
AI QA Pipeline Installer

  USAGE:

    npx ai-qa-workflow init [--yes]     Install into current directory
    npx ai-qa-workflow update [--yes]   Update pipeline files (preserves config)

  Or from the template directory:

    node install.js <target> [--yes]        Fresh install into target project
    node install.js <target> --update       Update existing installation
    node install.js . --yes                 Install into current directory

  FLAGS:
    --yes, -y    Skip all prompts
    --update,-u  Update mode (preserves config, stories, results)

  EXAMPLES:
    npx ai-qa-workflow init --yes
    node install.js ../my-project --yes
    node install.js ../my-project --update
`);
  process.exit(0);
}

async function main() {
  let targetPath;
  let mode = 'install';

  // Parse flags and target
  const nonFlagArgs = args.filter(a => !a.startsWith('-'));

  if (IS_SELF || args.includes('init')) {
    targetPath = process.cwd();
    mode = 'install';
  } else if (args.includes('update')) {
    targetPath = process.cwd();
    mode = 'update';
  } else if (IS_UPDATE) {
    targetPath = path.resolve(nonFlagArgs[0] || process.cwd());
    mode = 'update';
  } else if (nonFlagArgs.length > 0) {
    targetPath = path.resolve(nonFlagArgs[0]);
    mode = 'install';
  } else {
    targetPath = process.cwd();
    mode = 'install';
  }

  if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isDirectory()) {
    console.error(`  ✗ Target not found: ${targetPath}`);
    process.exit(1);
  }

  // Confirm unless --yes
  if (!YES) {
    const modeLabel = mode === 'update' ? 'UPDATE pipeline files' : 'INSTALL pipeline';
    const answer = await ask(`  ${modeLabel} in "${path.basename(targetPath)}"? (Y/n) `);
    if (answer.startsWith('n')) { console.log('  Aborted.'); process.exit(0); }
  }

  await install(targetPath, mode);
}

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