#!/usr/bin/env node
/**
 * Migration script - Migrate data from JSON files to database
 */

const fs = require('fs');
const path = require('path');
const { initDatabase, getDatabase, closeDatabase } = require('../src/database/connection');
const { initEncryption, encrypt } = require('../src/utils/encryption');
const { STATE_DIR, DB_PATH, KEY_FILE } = require('../src/database/config');

const DATABASE_PATH = DB_PATH;
const DATABASE_ENCRYPTION_KEY = process.env.DATABASE_ENCRYPTION_KEY;
const DATABASE_USE_SQLCIPHER = process.env.DATABASE_USE_SQLCIPHER !== '0' && process.env.DATABASE_USE_SQLCIPHER !== 'false';

const USERS_FILE = path.join(STATE_DIR, 'users.json');
const SESSIONS_FILE = path.join(STATE_DIR, 'user-sessions.json');
const AFFILIATES_FILE = path.join(STATE_DIR, 'affiliates.json');

async function loadJsonFile(filePath, defaultValue = []) {
  try {
    const data = fs.readFileSync(filePath, 'utf8');
    return JSON.parse(data);
  } catch (error) {
    if (error.code === 'ENOENT') {
      console.log(`  File not found: ${filePath}, using default`);
      return defaultValue;
    }
    throw error;
  }
}

async function migrateUsers() {
  console.log('\n📦 Migrating users...');
  
  const users = await loadJsonFile(USERS_FILE, []);
  console.log(`  Found ${users.length} users in JSON`);
  
  if (users.length === 0) {
    console.log('  No users to migrate');
    return { success: 0, failed: 0 };
  }
  
  let success = 0;
  let failed = 0;
  
  const db = getDatabase();
  const stmt = db.prepare(`
    INSERT INTO users (
      id, email, email_encrypted, name, name_encrypted, password_hash,
      providers, email_verified, verification_token, verification_expires_at,
      reset_token, reset_expires_at, plan, billing_status, billing_email,
      payment_method_last4, subscription_renews_at, referred_by_affiliate_code,
      affiliate_attribution_at, affiliate_payouts, two_factor_secret,
      two_factor_enabled, created_at, updated_at, last_login_at, data
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      email = excluded.email,
      email_encrypted = excluded.email_encrypted,
      name = excluded.name,
      name_encrypted = excluded.name_encrypted,
      password_hash = excluded.password_hash,
      providers = excluded.providers,
      email_verified = excluded.email_verified,
      verification_token = excluded.verification_token,
      verification_expires_at = excluded.verification_expires_at,
      reset_token = excluded.reset_token,
      reset_expires_at = excluded.reset_expires_at,
      plan = excluded.plan,
      billing_status = excluded.billing_status,
      billing_email = excluded.billing_email,
      payment_method_last4 = excluded.payment_method_last4,
      subscription_renews_at = excluded.subscription_renews_at,
      referred_by_affiliate_code = excluded.referred_by_affiliate_code,
      affiliate_attribution_at = excluded.affiliate_attribution_at,
      affiliate_payouts = excluded.affiliate_payouts,
      two_factor_secret = excluded.two_factor_secret,
      two_factor_enabled = excluded.two_factor_enabled,
      created_at = excluded.created_at,
      updated_at = excluded.updated_at,
      last_login_at = excluded.last_login_at,
      data = excluded.data
  `);

  for (const user of users) {
    try {
      const normalizedEmail = (user.email || '').trim().toLowerCase();
      if (!normalizedEmail) {
        console.log('  Skipping user without email');
        failed++;
        continue;
      }
      const passwordHash = user.passwordHash || user.password_hash || user.password || '';
      const providers = Array.isArray(user.providers) ? user.providers : [];
      const affiliatePayouts = Array.isArray(user.affiliatePayouts) ? user.affiliatePayouts : [];
      const dataPayload = JSON.stringify({ ...user, email: normalizedEmail || user.email || '' });
      const emailEncrypted = normalizedEmail ? encrypt(normalizedEmail) : '';
      const nameEncrypted = user.name ? encrypt(user.name) : null;
      const twoFactorEncrypted = user.twoFactorSecret ? encrypt(user.twoFactorSecret) : null;

      stmt.run(
        user.id,
        normalizedEmail,
        emailEncrypted,
        user.name || null,
        nameEncrypted,
        passwordHash,
        JSON.stringify(providers),
        user.emailVerified ? 1 : 0,
        user.verificationToken || null,
        user.verificationExpiresAt || null,
        user.resetToken || null,
        user.resetExpiresAt || null,
        user.plan || 'hobby',
        user.billingStatus || 'active',
        user.billingEmail || user.email || '',
        user.paymentMethodLast4 || '',
        user.subscriptionRenewsAt || null,
        user.referredByAffiliateCode || null,
        user.affiliateAttributionAt || null,
        JSON.stringify(affiliatePayouts),
        twoFactorEncrypted,
        user.twoFactorEnabled ? 1 : 0,
        user.createdAt || Date.now(),
        user.updatedAt || user.createdAt || Date.now(),
        user.lastLoginAt || null,
        dataPayload
      );
      
      console.log(`  ✓ Migrated user: ${user.email}`);
      success++;
    } catch (error) {
      console.error(`  ✗ Failed to migrate user ${user.email}:`, error.message);
      failed++;
    }
  }
  
  console.log(`  Completed: ${success} success, ${failed} failed`);
  return { success, failed };
}

async function migrateSessions() {
  console.log('\n📦 Migrating sessions...');
  
  const sessions = await loadJsonFile(SESSIONS_FILE, {});
  const sessionCount = Object.keys(sessions).length;
  console.log(`  Found ${sessionCount} sessions in JSON`);
  
  if (sessionCount === 0) {
    console.log('  No sessions to migrate');
    return { success: 0, failed: 0, expired: 0 };
  }
  
  const now = Date.now();
  let success = 0;
  let failed = 0;
  let expired = 0;
  
  const db = getDatabase();
  const userExistsStmt = db.prepare('SELECT id FROM users WHERE id = ?');
  const sessionStmt = db.prepare(`
    INSERT INTO sessions (
      id, user_id, token, refresh_token_hash, device_fingerprint,
      ip_address, user_agent, expires_at, created_at, last_accessed_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      user_id = excluded.user_id,
      token = excluded.token,
      expires_at = excluded.expires_at,
      last_accessed_at = excluded.last_accessed_at
  `);

  for (const [token, session] of Object.entries(sessions)) {
    try {
      // Skip expired sessions
      if (session.expiresAt && session.expiresAt <= now) {
        expired++;
        continue;
      }
      
      // Check if user exists
      const user = userExistsStmt.get(session.userId);
      if (!user) {
        console.log(`  Skipping session for non-existent user: ${session.userId}`);
        failed++;
        continue;
      }
      
      sessionStmt.run(
        token,
        session.userId,
        token,
        null,
        session.deviceFingerprint || null,
        session.ipAddress || null,
        session.userAgent || null,
        session.expiresAt,
        session.createdAt || now,
        session.lastAccessedAt || now
      );
      
      success++;
    } catch (error) {
      console.error(`  ✗ Failed to migrate session:`, error.message);
      failed++;
    }
  }
  
  console.log(`  Completed: ${success} success, ${failed} failed, ${expired} expired`);
  return { success, failed, expired };
}

async function migrateAffiliates() {
  console.log('\n📦 Migrating affiliates...');
  
  const affiliates = await loadJsonFile(AFFILIATES_FILE, []);
  console.log(`  Found ${affiliates.length} affiliates in JSON`);
  
  if (affiliates.length === 0) {
    console.log('  No affiliates to migrate');
    return { success: 0, failed: 0 };
  }
  
  let success = 0;
  let failed = 0;
  
  const db = getDatabase();
  const stmt = db.prepare(`
    INSERT INTO affiliate_accounts (
      id, email, name, password_hash, codes, earnings, commission_rate,
      created_at, updated_at, last_login_at, last_payout_at,
      email_verified, verification_token, verification_expires_at
    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ON CONFLICT(id) DO UPDATE SET
      email = excluded.email,
      name = excluded.name,
      password_hash = excluded.password_hash,
      codes = excluded.codes,
      earnings = excluded.earnings,
      commission_rate = excluded.commission_rate,
      updated_at = excluded.updated_at,
      last_login_at = excluded.last_login_at,
      last_payout_at = excluded.last_payout_at,
      email_verified = excluded.email_verified,
      verification_token = excluded.verification_token,
      verification_expires_at = excluded.verification_expires_at
  `);

  for (const affiliate of affiliates) {
    try {
      if (!affiliate.email) {
        console.log('  Skipping affiliate without email');
        failed++;
        continue;
      }
      const affiliateEmail = (affiliate.email || '').trim().toLowerCase();
      stmt.run(
        affiliate.id || require('crypto').randomUUID(),
        affiliateEmail,
        affiliate.name || '',
        affiliate.password || affiliate.passwordHash || '',
        JSON.stringify(affiliate.codes || []),
        JSON.stringify(affiliate.earnings || []),
        affiliate.commissionRate || 0.15,
        affiliate.createdAt || new Date().toISOString(),
        affiliate.updatedAt || affiliate.createdAt || new Date().toISOString(),
        affiliate.lastLoginAt || null,
        affiliate.lastPayoutAt || null,
        affiliate.emailVerified ? 1 : 0,
        affiliate.verificationToken || null,
        affiliate.verificationExpiresAt || null
      );
      
      console.log(`  ✓ Migrated affiliate: ${affiliate.email}`);
      success++;
    } catch (error) {
      console.error(`  ✗ Failed to migrate affiliate:`, error.message);
      failed++;
    }
  }
  
  console.log(`  Completed: ${success} success, ${failed} failed`);
  return { success, failed };
}

async function createBackup() {
  console.log('\n💾 Creating backup of JSON files...');
  
  const backupDir = path.join(DATA_ROOT, `migration_backup_${Date.now()}`);
  
  if (!fs.existsSync(backupDir)) {
    fs.mkdirSync(backupDir, { recursive: true });
  }
  
  const files = [USERS_FILE, SESSIONS_FILE, AFFILIATES_FILE];
  let backedUp = 0;
  
  for (const file of files) {
    if (fs.existsSync(file)) {
      const fileName = path.basename(file);
      const backupPath = path.join(backupDir, fileName);
      fs.copyFileSync(file, backupPath);
      console.log(`  ✓ Backed up: ${fileName}`);
      backedUp++;
    }
  }
  
  console.log(`  Created backup in: ${backupDir}`);
  console.log(`  Backed up ${backedUp} files`);
  
  return backupDir;
}

async function runMigration() {
  console.log('🔄 Starting database migration...');
  console.log('  Source: JSON files in', STATE_DIR);
  console.log('  Target: Database at', DATABASE_PATH);
  
  // Check if database exists
  if (!fs.existsSync(DATABASE_PATH)) {
    console.error('❌ Database not found. Please run setup-database.js first.');
    process.exit(1);
  }
  
  // Initialize encryption
  let encryptionKey = DATABASE_ENCRYPTION_KEY;
  if (!encryptionKey && KEY_FILE && fs.existsSync(KEY_FILE)) {
    encryptionKey = fs.readFileSync(KEY_FILE, 'utf8').trim();
    if (encryptionKey) {
      process.env.DATABASE_ENCRYPTION_KEY = encryptionKey;
      console.log('✅ Loaded encryption key from file');
    }
  }
  if (!encryptionKey) {
    console.error('❌ DATABASE_ENCRYPTION_KEY not set');
    process.exit(1);
  }
  
  try {
    initEncryption(encryptionKey);
    console.log('✅ Encryption initialized');
  } catch (error) {
    console.error('❌ Failed to initialize encryption:', error.message);
    process.exit(1);
  }
  
  // Initialize database
  try {
    initDatabase(DATABASE_PATH, {
      verbose: false,
      sqlcipherKey: DATABASE_USE_SQLCIPHER ? encryptionKey : null,
      cipherCompatibility: process.env.DATABASE_CIPHER_COMPAT || 4,
      kdfIter: process.env.DATABASE_KDF_ITER || 64000
    });
    console.log('✅ Database connected');
  } catch (error) {
    console.error('❌ Failed to connect to database:', error.message);
    process.exit(1);
  }
  
  // Create backup
  const backupDir = await createBackup();
  
  // Run migrations
  const results = {
    users: await migrateUsers(),
    sessions: await migrateSessions(),
    affiliates: await migrateAffiliates()
  };
  
  // Close database
  closeDatabase();
  
  // Print summary
  console.log('\n📊 Migration Summary:');
  console.log('  Users:');
  console.log(`    ✓ Success: ${results.users.success}`);
  console.log(`    ✗ Failed: ${results.users.failed}`);
  console.log('  Sessions:');
  console.log(`    ✓ Success: ${results.sessions.success}`);
  console.log(`    ✗ Failed: ${results.sessions.failed}`);
  console.log(`    ⏰ Expired: ${results.sessions.expired}`);
  console.log('  Affiliates:');
  console.log(`    ✓ Success: ${results.affiliates.success}`);
  console.log(`    ✗ Failed: ${results.affiliates.failed}`);
  
  const totalSuccess = results.users.success + results.sessions.success + results.affiliates.success;
  const totalFailed = results.users.failed + results.sessions.failed + results.affiliates.failed;
  
  console.log('\n  Total:');
  console.log(`    ✓ Success: ${totalSuccess}`);
  console.log(`    ✗ Failed: ${totalFailed}`);
  
  console.log('\n✅ Migration complete!');
  console.log(`  Backup created in: ${backupDir}`);
  console.log('\nNext steps:');
  console.log('  1. Verify migration: node scripts/verify-migration.js');
  console.log('  2. Test the application with: USE_JSON_DATABASE=1 npm start');
  console.log('  3. Switch to database mode: unset USE_JSON_DATABASE && npm start');
}

// Run migration
runMigration().catch(error => {
  console.error('❌ Migration failed:', error);
  closeDatabase();
  process.exit(1);
});
