// cli/commands/install.js
// clauth install — full provisioning command
// Called automatically by the bootstrap installer, or manually: clauth install
//
// Does:
//   1. Checks prerequisites (git, node, internet)
//   2. Collects Supabase project ref + PAT
//   3. Runs SQL migrations
//   4. Deploys auth-vault Edge Function
//   5. Generates HMAC salt + bootstrap token, stores as project secrets
//   6. Saves local config (Supabase URL + anon key)
//   7. Runs end-to-end test
//   8. Installs Claude skill
//   9. Cleans up
//  10. Prints: "clauth ready. Type clauth --help"

import { createInterface } from 'readline';
import { randomBytes } from 'crypto';
import { readFileSync, existsSync, mkdirSync, cpSync, rmSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { execSync } from 'child_process';
import Conf from 'conf';
import { getConfOptions } from '../conf-path.js';
import chalk from 'chalk';
import ora from 'ora';

const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT      = join(__dirname, '..', '..');
const MGMT      = 'https://api.supabase.com/v1';
const SKILLS_DIR = process.env.CLAUTH_SKILLS_DIR ||
  (process.platform === 'win32'
    ? join(process.env.USERPROFILE || '', '.claude', 'skills')
    : join(process.env.HOME || '', '.claude', 'skills'));

// ─────────────────────────────────────────────
// Prompt helpers — single shared readline to
// avoid losing buffered stdin between prompts
// ─────────────────────────────────────────────

let _rl;
function getRL() {
  if (!_rl) {
    _rl = createInterface({ input: process.stdin, output: process.stdout });
  }
  return _rl;
}
function closeRL() {
  if (_rl) { _rl.close(); _rl = null; }
}

function ask(question) {
  return new Promise(res => getRL().question(question, a => res(a.trim())));
}

function askSecret(label) {
  // In non-TTY (piped input, CI, Git Bash on Windows), just read a line
  return ask(label);
}

// ─────────────────────────────────────────────
// Supabase Management API
// ─────────────────────────────────────────────

async function mgmt(pat, method, path, body) {
  const res = await fetch(`${MGMT}${path}`, {
    method,
    headers: { 'Authorization': `Bearer ${pat}`, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  if (!res.ok) {
    const text = await res.text().catch(() => res.statusText);
    throw new Error(`${method} ${path} → HTTP ${res.status}: ${text}`);
  }
  if (res.status === 204) return {};
  const text = await res.text();
  if (!text) return {};
  return JSON.parse(text);
}

// ─────────────────────────────────────────────
// Main install command
// ─────────────────────────────────────────────

export async function runInstall(opts = {}) {
  console.log(chalk.cyan('\n🔐 clauth install\n'));

  // ── Step 0: Upgrade detection ──────────────
  const config0 = new Conf(getConfOptions());
  if (config0.get('supabase_url')) {
    console.log(chalk.cyan('  Existing installation detected.\n'));
    console.log(chalk.gray(`  Vault:  ${config0.get('supabase_url')}`));
    console.log(chalk.gray(`  Config: ${config0.path}\n`));

    const inquirerMod = await import('inquirer');
    const { action } = await inquirerMod.default.prompt([{
      type: 'list',
      name: 'action',
      message: 'What would you like to do?',
      choices: [
        { name: 'Upgrade (keep config, redeploy Edge Function + migrations)', value: 'upgrade' },
        { name: 'Fresh install (new vault)', value: 'fresh' },
        { name: 'Cancel', value: 'cancel' },
      ],
    }]);

    if (action === 'cancel') {
      console.log(chalk.gray('\n  Cancelled.\n'));
      return;
    }

    if (action === 'upgrade') {
      // Upgrade path: skip provisioning, just redeploy Edge Function and run migrations
      console.log(chalk.cyan('\n  Running upgrade...\n'));

      let ref = opts.ref;
      let pat = opts.pat;
      if (!ref || !pat) {
        console.log(chalk.gray('  Need Supabase PAT to redeploy Edge Function + run migrations.\n'));
        if (!ref) ref = await ask(chalk.white('Supabase project ref: '));
        if (!pat) pat = await askSecret(chalk.white('Supabase PAT: '));
        closeRL();
      }
      if (!ref || !pat) {
        console.log(chalk.red('\n  Both required.\n')); process.exit(1);
      }

      // Run migrations
      const s4 = ora('Running database migrations...').start();
      const migrations = [
        { name: '001_clauth_schema',  file: 'supabase/migrations/001_clauth_schema.sql'  },
        { name: '002_vault_helpers',  file: 'supabase/migrations/002_vault_helpers.sql'  },
        { name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
      ];
      for (const m of migrations) {
        const sql = readFileSync(join(ROOT, m.file), 'utf8');
        try {
          await mgmt(pat, 'POST', `/projects/${ref}/database/query`, { query: sql });
          s4.text = `Migrations: ${m.name} ✓`;
        } catch (e) {
          s4.fail(`Migration ${m.name} failed: ${e.message}`);
          process.exit(1);
        }
      }
      s4.succeed('Database migrations applied');

      // Redeploy Edge Function
      const s5 = ora('Redeploying auth-vault Edge Function...').start();
      const fnSource = readFileSync(join(ROOT, 'supabase/functions/auth-vault/index.ts'), 'utf8');
      try {
        const formData = new FormData();
        const metadata = {
          name: 'auth-vault',
          entrypoint_path: 'index.ts',
          verify_jwt: true,
        };
        formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
        formData.append('file', new Blob([fnSource], { type: 'application/typescript' }), 'index.ts');

        const deployRes = await fetch(
          `${MGMT}/projects/${ref}/functions/deploy?slug=auth-vault`,
          {
            method: 'POST',
            headers: { 'Authorization': `Bearer ${pat}` },
            body: formData,
          }
        );
        if (!deployRes.ok) {
          const errText = await deployRes.text().catch(() => deployRes.statusText);
          throw new Error(`HTTP ${deployRes.status}: ${errText}`);
        }
        s5.succeed('auth-vault Edge Function redeployed');
      } catch (e) {
        s5.warn(`Edge Function deploy failed: ${e.message}`);
        console.log(chalk.yellow('  Run manually: supabase functions deploy auth-vault'));
      }

      // Reinstall Claude skill
      const s9 = ora('Updating Claude skill...').start();
      const skillSrc = join(ROOT, '.clauth-skill');
      if (existsSync(skillSrc)) {
        try {
          const skillDest = join(SKILLS_DIR, 'clauth');
          mkdirSync(skillDest, { recursive: true });
          cpSync(skillSrc, skillDest, { recursive: true, force: true });
          s9.succeed(`Claude skill updated → ${skillDest}`);
        } catch (e) {
          s9.warn(`Skill update skipped: ${e.message}`);
        }
      } else {
        s9.warn('Skill directory not found — skipping');
      }

      console.log('');
      console.log(chalk.green('  Upgrade complete.'));
      console.log(chalk.gray('  Run clauth test to verify.\n'));
      return;
    }
    // action === 'fresh' — continue with normal install below
  }

  // ── Step 1: Check internet ────────────────
  const s1 = ora('Checking connectivity...').start();
  try {
    await fetch('https://api.supabase.com/v1/projects', {
      method: 'GET', headers: { 'Authorization': 'Bearer test' }
    });
    s1.succeed('Connected');
  } catch {
    s1.fail('No internet connection. Check your network and retry.');
    process.exit(1);
  }

  // ── Step 2: Collect credentials ───────────
  let ref = opts.ref;
  let pat = opts.pat;

  if (!ref || !pat) {
    console.log(chalk.cyan('\nYou need two things from Supabase:\n'));
    console.log(chalk.gray('  Project ref:  last segment of your project URL'));
    console.log(chalk.gray('  e.g. supabase.com/dashboard/project/') + chalk.white('uvojezuorjgqzmhhgluu'));
    console.log('');
    console.log(chalk.gray('  Personal Access Token (PAT):'));
    console.log(chalk.gray('  supabase.com/dashboard/account/tokens → Generate new token'));
    console.log(chalk.gray('  (This is NOT your anon key or service_role key)\n'));

    if (!ref) ref = await ask(chalk.white('Supabase project ref: '));
    if (!pat) pat = await askSecret(chalk.white('Supabase PAT: '));
    closeRL();
  }

  if (!ref || !pat) {
    console.log(chalk.red('\n✗ Both required.\n')); process.exit(1);
  }

  // ── Step 3: Verify + fetch keys ──────────
  const s3 = ora('Verifying Supabase credentials...').start();
  let anon, serviceRole;
  try {
    const keys = await mgmt(pat, 'GET', `/projects/${ref}/api-keys`);
    anon        = keys.find(k => k.name === 'anon')?.api_key;
    serviceRole = keys.find(k => k.name === 'service_role')?.api_key;
    if (!anon || !serviceRole) throw new Error('API keys not found in response');
    s3.succeed('Supabase project verified');
  } catch (e) {
    s3.fail(`Could not connect: ${e.message}`);
    console.log(chalk.gray('  Check your project ref and PAT.'));
    process.exit(1);
  }

  const projectUrl = `https://${ref}.supabase.co`;

  // ── Step 4: Run migrations ────────────────
  const s4 = ora('Running database migrations...').start();
  const migrations = [
    { name: '001_clauth_schema',  file: 'supabase/migrations/001_clauth_schema.sql'  },
    { name: '002_vault_helpers',  file: 'supabase/migrations/002_vault_helpers.sql'  },
    { name: '003_machine_enrollments', file: 'supabase/migrations/003_machine_enrollments.sql' },
  ];
  for (const m of migrations) {
    const sql = readFileSync(join(ROOT, m.file), 'utf8');
    try {
      await mgmt(pat, 'POST', `/projects/${ref}/database/query`, { query: sql });
      s4.text = `Migrations: ${m.name} ✓`;
    } catch (e) {
      s4.fail(`Migration ${m.name} failed: ${e.message}`);
      process.exit(1);
    }
  }
  s4.succeed('Database migrations applied');

  // ── Step 5: Deploy Edge Function ─────────
  const s5 = ora('Deploying auth-vault Edge Function...').start();
  const fnSource = readFileSync(join(ROOT, 'supabase/functions/auth-vault/index.ts'), 'utf8');
  try {
    // Use the /deploy endpoint with multipart/form-data (not the old /functions endpoint)
    const formData = new FormData();

    const metadata = {
      name: 'auth-vault',
      entrypoint_path: 'index.ts',
      verify_jwt: true,
    };
    formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
    formData.append('file', new Blob([fnSource], { type: 'application/typescript' }), 'index.ts');

    const deployRes = await fetch(
      `${MGMT}/projects/${ref}/functions/deploy?slug=auth-vault`,
      {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${pat}` },
        body: formData,
      }
    );

    if (!deployRes.ok) {
      const errText = await deployRes.text().catch(() => deployRes.statusText);
      throw new Error(`HTTP ${deployRes.status}: ${errText}`);
    }

    s5.succeed('auth-vault Edge Function deployed');
  } catch (e) {
    s5.warn(`Edge Function deploy failed: ${e.message}`);
    console.log(chalk.yellow('  Run manually: supabase functions deploy auth-vault'));
  }

  // ── Step 6: Generate + store secrets ─────
  const s6 = ora('Generating secrets...').start();
  const hmacSalt       = randomBytes(32).toString('hex');
  const bootstrapToken = randomBytes(16).toString('hex');
  try {
    await mgmt(pat, 'POST', `/projects/${ref}/secrets`, [
      { name: 'CLAUTH_HMAC_SALT',              value: hmacSalt       },
      { name: 'CLAUTH_ADMIN_BOOTSTRAP_TOKEN',  value: bootstrapToken },
    ]);
    s6.succeed('Secrets generated and stored');
  } catch (e) {
    s6.warn(`Could not store secrets via API: ${e.message}`);
    console.log(chalk.yellow('\n  Set these manually in Supabase → Settings → Edge Functions → Secrets:'));
    console.log(chalk.white(`  CLAUTH_HMAC_SALT = ${hmacSalt}`));
    console.log(chalk.white(`  CLAUTH_ADMIN_BOOTSTRAP_TOKEN = ${bootstrapToken}\n`));
  }

  // ── Step 7: Save local config ─────────────
  const config = new Conf(getConfOptions());
  config.set('supabase_url', projectUrl);
  config.set('supabase_anon_key', anon);

  // ── Step 8: End-to-end test ───────────────
  const s8 = ora('Running end-to-end test...').start();
  try {
    // Register a test machine
    const testMachine = `install-test-${randomBytes(4).toString('hex')}`;
    const regRes = await fetch(`${projectUrl}/functions/v1/auth-vault/register-machine`, {
      method: 'POST',
      headers: { 'Authorization': `Bearer ${anon}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        machine_hash: testMachine, hmac_seed_hash: 'test',
        label: 'install-test', admin_token: bootstrapToken,
      }),
    });
    const reg = await regRes.json();
    if (!reg.success) throw new Error(`register-machine: ${JSON.stringify(reg)}`);
    s8.succeed('End-to-end test passed — Edge Function is live');
  } catch (e) {
    s8.warn(`Test failed (Edge Function may still be deploying): ${e.message}`);
    console.log(chalk.yellow('  Run clauth test after a minute to verify.'));
  }

  // ── Step 9: Install Claude skill ──────────
  const s9 = ora('Installing Claude skill...').start();
  const skillSrc = join(ROOT, '.clauth-skill');
  if (existsSync(skillSrc)) {
    try {
      const skillDest = join(SKILLS_DIR, 'clauth');
      mkdirSync(skillDest, { recursive: true });
      cpSync(skillSrc, skillDest, { recursive: true, force: true });
      s9.succeed(`Claude skill installed → ${skillDest}`);
    } catch (e) {
      s9.warn(`Skill install skipped: ${e.message}`);
    }
  } else {
    s9.warn('Skill directory not found — skipping');
  }

  // ── Step 9.5: Install MCP server config ───
  const s95 = ora('Configuring MCP server for Claude Code...').start();
  try {
    const mcpJsonPath = join(
      process.platform === 'win32' ? process.env.USERPROFILE : process.env.HOME,
      '.mcp.json'
    );
    const cliEntry = join(ROOT, 'cli', 'index.js').replace(/\\/g, '/');
    let mcpConfig = {};
    if (existsSync(mcpJsonPath)) {
      try { mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8')); } catch {}
    }
    if (!mcpConfig.mcpServers) mcpConfig.mcpServers = {};
    mcpConfig.mcpServers.clauth = {
      command: 'node',
      args: [cliEntry, 'serve', '--action', 'mcp'],
    };
    const { writeFileSync } = await import('fs');
    writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
    s95.succeed(`MCP server config → ${mcpJsonPath}`);
  } catch (e) {
    s95.warn(`MCP config skipped: ${e.message}`);
    console.log(chalk.yellow('  Add manually to ~/.mcp.json:'));
    console.log(chalk.gray(`  { "mcpServers": { "clauth": { "command": "node", "args": ["${join(ROOT, 'cli', 'index.js').replace(/\\/g, '/')}", "serve", "--action", "mcp"] } } }`));
  }

  // ── Step 10: Done ─────────────────────────
  console.log('');
  console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
  console.log(chalk.green('  ✓ clauth installed and ready'));
  console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
  console.log('');
  console.log(chalk.yellow('  Save this bootstrap token — you need it once for clauth setup:'));
  console.log(chalk.white('  ' + bootstrapToken));
  console.log('');
  console.log(chalk.gray('  (Also stored in Supabase → Settings → Edge Functions → Secrets)'));
  console.log('');
  console.log(chalk.white('  Next: run') + chalk.cyan('  clauth setup'));
  console.log(chalk.gray('  Then:') + chalk.cyan('     clauth test'));
  console.log('');
}
