#!/usr/bin/env node

/**
 * DashClaw interactive setup.
 *
 * One command to go from zero to a truthful local/cloud-ready summary:
 *   node scripts/setup.mjs
 *
 * Handles: env config, dependency install, database migrations, build.
 * Cross-platform (Windows, Mac, Linux). Safe to re-run.
 */

process.on('unhandledRejection', (reason) => {
  console.error('Unhandled Rejection:', reason);
  process.exit(1);
});

import fs from 'node:fs';
import crypto from 'node:crypto';
import { execFileSync, spawn } from 'node:child_process';
import readline from 'node:readline';
import { getAuthConfig } from '../app/lib/authConfig.mjs';
import {
  buildSetupMigrationCommands,
  SETUP_MIGRATION_SCRIPTS,
} from '../app/lib/setup/runtime-prerequisites.mjs';

function ask(question) {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  return new Promise((resolve) => {
    rl.question(question, (answer) => {
      rl.close();
      resolve(answer.trim());
    });
  });
}

function askSecret(question) {
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
  const onData = (char) => {
    const text = String(char);
    if (text === '\n' || text === '\r' || text === '\u0004') {
      process.stdout.write('\n');
      return;
    }
    const prompt = `${question}${'*'.repeat(rl.line.length)}`;
    process.stdout.write(`\x1B[2K\x1B[200D${prompt}`);
  };

  return new Promise((resolve) => {
    process.stdin.on('data', onData);
    rl.question(question, (answer) => {
      process.stdin.removeListener('data', onData);
      rl.close();
      resolve(answer.trim());
    });
  });
}

function banner(text) {
  const line = '='.repeat(48);
  console.log(`\n  ${line}\n   ${text}\n  ${line}\n`);
}

function step(n, total, msg) {
  console.log(`\n  [${n}/${total}] ${msg}\n`);
}

function ok(msg) {
  console.log(`  [ok] ${msg}`);
}

function warn(msg) {
  console.log(`  [warn] ${msg}`);
}

function fail(msg) {
  console.error(`  [fail] ${msg}`);
}

function run(cmd, args, opts = {}) {
  try {
    execFileSync(cmd, args, { stdio: 'inherit', shell: true, ...opts });
    return true;
  } catch {
    return false;
  }
}

function runQuiet(cmd, args, opts = {}) {
  try {
    const stdout = execFileSync(cmd, args, {
      stdio: ['ignore', 'pipe', 'pipe'],
      encoding: 'utf8',
      shell: false,
      ...opts,
    });
    return { ok: true, stdout: String(stdout || '').trim() };
  } catch (error) {
    return {
      ok: false,
      stdout: String(error.stdout || '').trim(),
      stderr: String(error.stderr || '').trim(),
    };
  }
}

function parseEnvFile(text) {
  const env = {};
  for (const line of text.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
    const idx = trimmed.indexOf('=');
    if (idx === -1) continue;

    const key = trimmed.slice(0, idx).trim();
    let value = trimmed.slice(idx + 1).trim();
    if (
      (value.startsWith('"') && value.endsWith('"')) ||
      (value.startsWith("'") && value.endsWith("'"))
    ) {
      value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\'/g, "'");
    }
    env[key] = value;
  }
  return env;
}

function formatEnvValue(value) {
  const text = String(value ?? '');
  if (!/[\s"'#]/.test(text)) return text;
  return `"${text.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
}

function writeEnvFile(env) {
  const order = [
    'DASHCLAW_MODE',
    'NEXT_PUBLIC_DASHCLAW_MODE',
    'DATABASE_URL',
    'DASHCLAW_DB_DRIVER',
    'NEXTAUTH_URL',
    'NEXTAUTH_SECRET',
    'DASHCLAW_LOCAL_ADMIN_PASSWORD',
    'DASHCLAW_API_KEY',
    'DASHCLAW_API_KEY_ORG',
    'ENCRYPTION_KEY',
    'CRON_SECRET',
  ];
  const lines = [`# Generated by scripts/setup.mjs on ${new Date().toISOString()}`];
  for (const key of order) {
    if (env[key] != null) lines.push(`${key}=${formatEnvValue(env[key])}`);
  }
  const extras = Object.keys(env).filter((key) => !order.includes(key)).sort();
  if (extras.length) {
    lines.push('', '# Additional settings');
    for (const key of extras) lines.push(`${key}=${formatEnvValue(env[key])}`);
  }
  fs.writeFileSync('.env.local', `${lines.join('\n')}\n`, 'utf8');
}

function b64url(n) {
  return crypto
    .randomBytes(n)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/g, '');
}

function redactSecret(value, keep = 4) {
  if (!value) return '(unset)';
  const text = String(value);
  if (text.length <= keep) return '*'.repeat(text.length);
  return `${'*'.repeat(Math.max(8, text.length - keep))}${text.slice(-keep)}`;
}

function describeSignInMethods(authConfig) {
  const methods = [];
  if (authConfig.hasLocalPassword) methods.push('local admin password');
  for (const provider of authConfig.oauthProviders) methods.push(provider.name);
  return methods;
}

async function runAsync(cmd, args, opts = {}) {
  return new Promise((resolve) => {
    let child;
    try {
      child = spawn(cmd, args, { shell: true, stdio: 'pipe', ...opts });
    } catch (error) {
      resolve({ code: 1, stderr: error.message || 'spawn error' });
      return;
    }
    let stderr = '';
    child.stderr?.on('data', (data) => {
      stderr += String(data);
    });
    child.on('close', (code) => resolve({ code, stderr }));
    child.on('error', () => resolve({ code: 1, stderr: 'spawn error' }));
  });
}

async function chooseDatabaseUrl(env) {
  const dockerInstalled = runQuiet('docker', ['--version']).ok;

  if (!env.DATABASE_URL) {
    while (!env.DATABASE_URL) {
      console.log('  You need a PostgreSQL database. Pick one:\n');

      if (dockerInstalled) {
        console.log('  [1] Local Docker (fastest for dev)');
        console.log("      Requires Docker Desktop. We'll start it for you.\n");
      } else {
        console.log('  [1] Local Docker (unavailable)');
        console.log('      Docker was not found on this machine. Use Neon or another Postgres URL instead.\n');
      }

      console.log('  [2] Neon (free cloud Postgres)');
      console.log('      Sign up at https://neon.tech, create a project, copy the URL.\n');
      console.log('  [3] Other Postgres');
      console.log('      Paste any postgresql:// connection string.\n');

      const choice = await ask('  Choose [1/2/3]: ');

      if (choice === '1') {
        if (!dockerInstalled) {
          warn('Docker is not installed. Install Docker Desktop or choose Neon/Other Postgres.');
          continue;
        }

        console.log('\n  Starting Docker Postgres...');
        if (!run('docker', ['compose', 'up', '-d', 'db'])) {
          warn('Docker was found, but DashClaw could not start the local database.');
          console.log('  Next step: start Docker Desktop, then re-run this step or choose Neon/Other Postgres.');
          continue;
        }

        env.DATABASE_URL = 'postgresql://dashclaw:dashclaw@localhost:5432/dashclaw';
        ok('Local Postgres is running on localhost:5432');
        break;
      }

      if (choice === '2' || choice === '3') {
        const url = await ask('  Paste your PostgreSQL URL: ');
        if (!url || !url.startsWith('postgresql://')) {
          warn('Invalid URL. It must start with postgresql://');
          continue;
        }
        env.DATABASE_URL = url;
        ok('Database URL saved');
        break;
      }

      warn('Invalid choice. Enter 1, 2, or 3.');
    }
  }

  return { dockerInstalled };
}

async function maybeConfigureLocalAdminPassword(env) {
  const authConfig = getAuthConfig(env);
  if (authConfig.hasLocalPassword) {
    ok('Local admin password already set');
    return;
  }

  const methods = describeSignInMethods(authConfig);
  if (methods.length > 0) {
    ok(`Dashboard sign-in already configured via ${methods.join(', ')}`);
    return;
  }

  console.log('  DashClaw needs a dashboard sign-in method.');
  console.log('  For solo/local use, the easiest path is a local admin password.');
  const answer = await ask('  Set a local admin password now? [Y/n]: ');
  if (answer.toLowerCase() === 'n') {
    warn('Skipped dashboard sign-in setup. You will need DASHCLAW_LOCAL_ADMIN_PASSWORD or OAuth before you can sign in.');
    return;
  }

  while (!env.DASHCLAW_LOCAL_ADMIN_PASSWORD) {
    const password = await askSecret('  Choose a local admin password (min 12 chars): ');
    if (!password) {
      warn('Password was blank. Setup will continue, but dashboard sign-in will stay unconfigured.');
      return;
    }
    if (password.length < 12) {
      warn('Use at least 12 characters.');
      continue;
    }

    const confirm = await askSecret('  Confirm password: ');
    if (password !== confirm) {
      warn('Passwords did not match. Try again.');
      continue;
    }

    env.DASHCLAW_LOCAL_ADMIN_PASSWORD = password;
    ok('Local admin password saved to .env.local');
  }
}

async function main() {
  banner('DashClaw Setup');

  const TOTAL = 6;
  const envPath = '.env.local';
  const hasEnv = fs.existsSync(envPath);
  const env = hasEnv ? parseEnvFile(fs.readFileSync(envPath, 'utf8')) : {};

  let dockerInstalled = false;
  let buildOk = false;
  let setupStatus = null;
  const migrationResults = [];

  step(1, TOTAL, 'Database connection');

  if (process.env.DATABASE_URL && process.env.DATABASE_URL !== env.DATABASE_URL) {
    env.DATABASE_URL = process.env.DATABASE_URL;
    ok(`Using DATABASE_URL from environment (${env.DATABASE_URL.replace(/\/\/[^@]+@/, '//***@').replace(/\?.*/, '')})`);
  } else if (env.DATABASE_URL) {
    const masked = env.DATABASE_URL.replace(/\/\/[^@]+@/, '//***@').replace(/\?.*/, '');
    ok(`DATABASE_URL already set (${masked})`);
    const change = await ask('  Change it? [y/N]: ');
    if (change.toLowerCase() === 'y') delete env.DATABASE_URL;
  }

  ({ dockerInstalled } = await chooseDatabaseUrl(env));

  step(2, TOTAL, 'Deployment mode');

  let deployUrl = env.NEXTAUTH_URL;
  if (!deployUrl || deployUrl === 'http://localhost:3000') {
    console.log('  How will you access the dashboard?\n');
    console.log('  [1] Local only (http://localhost:3000)');
    console.log('      Run "npm run dev" on this machine.\n');
    console.log('  [2] Cloud (Vercel, Railway, etc.)');
    console.log('      Access from anywhere.\n');

    const deployChoice = await ask('  Choose [1/2]: ');
    if (deployChoice === '2') {
      const url = await ask('  Your deployment URL (for example https://my-dashclaw.vercel.app): ');
      if (url && url.startsWith('http')) {
        deployUrl = url.replace(/\/+$/, '');
      } else {
        warn('Invalid URL. Defaulting to http://localhost:3000');
        deployUrl = 'http://localhost:3000';
      }
    } else {
      deployUrl = 'http://localhost:3000';
    }
  } else {
    ok(`Dashboard URL: ${deployUrl}`);
  }

  step(3, TOTAL, 'Secrets and dashboard login');

  if (!env.DASHCLAW_MODE) env.DASHCLAW_MODE = 'self_host';
  if (!env.NEXT_PUBLIC_DASHCLAW_MODE) env.NEXT_PUBLIC_DASHCLAW_MODE = env.DASHCLAW_MODE;
  env.NEXTAUTH_URL = deployUrl;
  if (!env.NEXTAUTH_SECRET) env.NEXTAUTH_SECRET = b64url(32);
  const apiKeyWasGenerated = !env.DASHCLAW_API_KEY;
  if (apiKeyWasGenerated) env.DASHCLAW_API_KEY = `oc_live_${crypto.randomBytes(24).toString('hex')}`;
  if (!env.ENCRYPTION_KEY) env.ENCRYPTION_KEY = b64url(32).slice(0, 32);
  if (!env.CRON_SECRET) env.CRON_SECRET = crypto.randomBytes(32).toString('hex');

  await maybeConfigureLocalAdminPassword(env);

  writeEnvFile(env);
  ok('Wrote .env.local');
  if (apiKeyWasGenerated) {
    // First run only: show the full key once so the user can save it.
    // On re-runs, .env.local already holds the key and we only print the
    // redacted summary below to avoid leaking it in scrollback.
    console.log('');
    console.log("  API Key (save this — it won't be shown in full again):");
    console.log(`    ${env.DASHCLAW_API_KEY}`);
  }
  console.log(`\n  API key: ${redactSecret(env.DASHCLAW_API_KEY)}`);
  console.log(`  Dashboard URL: ${deployUrl}`);

  step(4, TOTAL, 'Installing dependencies');

  if (!run('npm', ['install'])) {
    fail('npm install failed. Check your network connection and npm logs above.');
    process.exit(1);
  }
  ok('Dependencies installed');

  step(5, TOTAL, 'Running database migrations');

  const migrations = SETUP_MIGRATION_SCRIPTS;
  /*
    // Core schema (order matters — multi-tenant adds org_id everywhere)
    'scripts/migrate-multi-tenant.mjs',
    'scripts/migrate-action-records-compat.mjs',
    'scripts/migrate-cost-analytics.mjs',
    'scripts/migrate-identity-binding.mjs',
    'scripts/migrate-agent-pairings.mjs',
    'scripts/migrate-capabilities.mjs',
    'scripts/migrate-hitl-metadata.mjs',
    // Governance features
    'scripts/migrate-policy-agent-scope.mjs',
    'scripts/migrate-behavioral-ai.mjs',
    'scripts/migrate-token-budgets.mjs',
    'scripts/migrate-prompt-injection.mjs',
    // Intelligence features
    'scripts/migrate-evaluations.mjs',
    'scripts/migrate-scoring-profiles.mjs',
    'scripts/migrate-prompts.mjs',
    'scripts/migrate-feedback.mjs',
    'scripts/migrate-learning-loop-mvp.mjs',
    'scripts/migrate-learning-analytics.mjs',
    // Operational features
    'scripts/migrate-compliance-export.mjs',
    'scripts/migrate-drift.mjs',
    'scripts/migrate-agent-schedules.mjs',
    'scripts/migrate-message-attachments.mjs',
    'scripts/migrate-ideas-subscores.mjs',
    'scripts/migrate-agent-messages-index.mjs',
  */
  const migrationEnv = { ...process.env, ...env };
  const frames = ['-', '\\', '|', '/'];

  for (const script of migrations) {
    const name = script.replace('scripts/', '').replace('.mjs', '');
    const start = Date.now();
    let frame = 0;
    const spinner = setInterval(() => {
      const elapsed = Math.floor((Date.now() - start) / 1000);
      process.stdout.write(`\r  ${frames[frame++ % frames.length]} Running ${name}... (${elapsed}s)`);
    }, 100);

    const result = await runAsync('node', [script], { env: migrationEnv });
    clearInterval(spinner);

    if (result.code === 0) {
      process.stdout.write(`\r  [ok] ${name}${' '.repeat(30)}\n`);
      migrationResults.push({ script, ok: true });
    } else {
      process.stdout.write(`\r  [fail] ${name}${' '.repeat(30)}\n`);
      const firstLine = result.stderr.trim().split('\n')[0];
      if (firstLine) console.log(`     ${firstLine}`);
      migrationResults.push({ script, ok: false, error: firstLine || 'Migration failed' });
    }
  }

  const migrationFailures = migrationResults.filter((result) => !result.ok);
  if (migrationFailures.length === 0) {
    ok('All migrations completed');
  } else {
    warn(`${migrationFailures.length} migration(s) failed. DashClaw is not database-ready yet.`);
  }

  const { getSetupStatus } = await import('../app/lib/setupStatus.mjs');
  setupStatus = await getSetupStatus(migrationEnv);

  step(6, TOTAL, 'Building the dashboard');

  buildOk = run('npm', ['run', 'build'], { env: migrationEnv });
  if (buildOk) {
    ok('Build completed');
  } else {
    warn('Build failed. Fix the error above, then rerun npm run build.');
  }

  const authConfig = getAuthConfig(env);
  const signInMethods = describeSignInMethods(authConfig);
  const databaseReady = Boolean(setupStatus?.configured);
  const signInReady = authConfig.hasAnySignInMethod;
  const fullyReady = databaseReady && signInReady && buildOk;
  const isCloud = deployUrl !== 'http://localhost:3000';

  banner(fullyReady ? 'Setup Complete' : 'Setup Not Finished Yet');

  console.log('  What succeeded:');
  ok('.env.local was written with generated secrets');
  ok('Dependencies installed');
  if (migrationResults.some((result) => result.ok)) {
    ok(`${migrationResults.filter((result) => result.ok).length}/${migrationResults.length} migration scripts completed`);
  }
  if (buildOk) ok('Next.js production build completed');

  console.log('\n  Current status:');
  if (databaseReady) {
    ok('Database is reachable and core setup tables exist');
  } else if (setupStatus?.reason === 'connection_error') {
    warn('Database is not reachable yet. /api/setup/status would report connection_error.');
  } else if (setupStatus?.reason === 'no_tables') {
    warn(`Database is reachable, but ${setupStatus.missing_tables} core table(s) are still missing.`);
  } else {
    warn(setupStatus?.message || 'DashClaw setup status could not be verified.');
  }

  if (signInReady) {
    ok(`Dashboard sign-in is available via ${signInMethods.join(', ')}`);
  } else {
    warn('Dashboard sign-in is not configured yet.');
  }

  if (migrationFailures.length > 0) {
    warn(`Failed migrations: ${migrationFailures.map((result) => result.script.replace('scripts/', '')).join(', ')}`);
  }
  if (!buildOk) {
    warn('Build step did not finish cleanly.');
  }

  console.log('\n  Next steps:');
  let nextStepNumber = 1;

  if (!databaseReady) {
    if (setupStatus?.reason === 'connection_error') {
      if (env.DATABASE_URL?.includes('localhost')) {
        console.log(`  ${nextStepNumber}. Start or verify your local Postgres server.`);
        if (dockerInstalled) {
          console.log('     Command: docker compose up -d db');
        }
      } else {
        console.log(`  ${nextStepNumber}. Verify that DATABASE_URL points to a reachable Postgres instance.`);
      }
      nextStepNumber += 1;
    }

    console.log(`  ${nextStepNumber}. Re-run the migration step after the database is reachable:`);
    for (const command of buildSetupMigrationCommands()) {
      console.log(`     ${command}`);
    }
    nextStepNumber += 1;
  }

  if (!signInReady) {
    console.log(`  ${nextStepNumber}. Configure a dashboard login method.`);
    console.log('     Easiest local path: set DASHCLAW_LOCAL_ADMIN_PASSWORD in .env.local and rerun setup.');
    console.log('     Team path: add GitHub, Google, or OIDC environment variables.');
    nextStepNumber += 1;
  }

  if (!buildOk) {
    console.log(`  ${nextStepNumber}. Re-run the build once the error above is fixed.`);
    console.log('     Command: npm run build');
    nextStepNumber += 1;
  }

  if (databaseReady && signInReady && buildOk) {
    if (isCloud) {
      console.log(`  ${nextStepNumber}. Push this repo to GitHub, add the values from .env.local to your host, and deploy.`);
      nextStepNumber += 1;
      console.log(`  ${nextStepNumber}. Open ${deployUrl}/dashboard and sign in with ${signInMethods[0]}.`);
    } else {
      console.log(`  ${nextStepNumber}. Start the app locally.`);
      console.log('     Command: npm run dev');
      nextStepNumber += 1;
      console.log(`  ${nextStepNumber}. Open http://localhost:3000/dashboard and sign in with ${signInMethods[0]}.`);
    }
  } else {
    console.log(`  ${nextStepNumber}. Re-run node scripts/setup.mjs after you fix the items above.`);
  }

  console.log('\n  Agent connection snippet:\n');
  console.log(`    DASHCLAW_BASE_URL=${deployUrl}`);
  console.log(`    DASHCLAW_API_KEY=${env.DASHCLAW_API_KEY}`);
  console.log('    DASHCLAW_AGENT_ID=my-agent\n');
}

main();
