/**
 * Migration script: moves integration credentials and configurable settings
 * from environment variables into the database.
 *
 * Run with:
 *   npx ts-node -P tsconfig.json -e "require('./prisma/migrate-env-to-db.ts')"
 *
 * Or (with dotenv):
 *   node -r dotenv/config -r ts-node/register prisma/migrate-env-to-db.ts
 *
 * The script is idempotent - safe to run multiple times.
 */

import { PrismaClient, IntegrationType } from '@prisma/client';
import * as crypto from 'crypto';

const prisma = new PrismaClient();

// ---------------------------------------------------------------------------
// Encryption helpers - MUST match EncryptionService exactly
// ---------------------------------------------------------------------------

const IV_LENGTH = 16;   // 128 bits
const TAG_LENGTH = 16;  // 128 bits
const KEY_LENGTH = 32;  // 256 bits

/**
 * Derives a 256-bit key using PBKDF2 - matches EncryptionService.deriveKey()
 */
function deriveKey(password: string): Buffer {
  const salt = 'tos-encryption-salt-v1';
  return crypto.pbkdf2Sync(password, salt, 100000, KEY_LENGTH, 'sha256');
}

/**
 * Builds the encryption key buffer - matches EncryptionService.onModuleInit()
 */
function buildEncryptionKey(encryptionKey: string): Buffer {
  if (encryptionKey.length < KEY_LENGTH) {
    return deriveKey(encryptionKey);
  }
  // Use first 32 UTF-8 bytes when key is long enough
  return Buffer.from(encryptionKey.slice(0, KEY_LENGTH), 'utf-8');
}

/**
 * Encrypts a JSON object - matches EncryptionService.encryptObject()
 *
 * Format: base64( IV[16] || ciphertext || authTag[16] )
 */
function encryptObject(data: Record<string, string>, encryptionKeyRaw: string): string {
  const json = JSON.stringify(data);
  const key = buildEncryptionKey(encryptionKeyRaw);
  const iv = crypto.randomBytes(IV_LENGTH);

  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, {
    authTagLength: TAG_LENGTH,
  });

  const encrypted = Buffer.concat([
    cipher.update(json, 'utf8'),
    cipher.final(),
  ]);

  const authTag = cipher.getAuthTag();

  // Combine IV + ciphertext + authTag, encode as base64
  const combined = Buffer.concat([iv, encrypted, authTag]);
  return combined.toString('base64');
}

// ---------------------------------------------------------------------------
// Migration
// ---------------------------------------------------------------------------

async function migrate() {
  console.log('Starting .env to DB migration...\n');

  // 1. Migrate system settings
  const settingsMap: Record<
    string,
    { key: string; category: string; valueType: string; description: string }
  > = {
    ENABLE_SYNC_JOBS: {
      key: 'feature.syncJobs.enabled',
      category: 'feature',
      valueType: 'boolean',
      description: 'Hintergrund-Sync-Jobs aktivieren',
    },
    SWAGGER_ENABLED: {
      key: 'feature.swagger.enabled',
      category: 'feature',
      valueType: 'boolean',
      description: 'Swagger API-Dokumentation aktivieren',
    },
    CORS_ORIGINS: {
      key: 'cors.origins',
      category: 'cors',
      valueType: 'string',
      description: 'Erlaubte CORS Origins (kommagetrennt)',
    },
    SYNC_INTERVAL_PLENTYONE: {
      key: 'sync.interval.plentyone',
      category: 'sync',
      valueType: 'number',
      description: 'PlentyONE Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_ZULIP: {
      key: 'sync.interval.zulip',
      category: 'sync',
      valueType: 'number',
      description: 'Zulip Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_TODOIST: {
      key: 'sync.interval.todoist',
      category: 'sync',
      valueType: 'number',
      description: 'Todoist Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_FREESCOUT: {
      key: 'sync.interval.freescout',
      category: 'sync',
      valueType: 'number',
      description: 'FreeScout Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_NEXTCLOUD: {
      key: 'sync.interval.nextcloud',
      category: 'sync',
      valueType: 'number',
      description: 'Nextcloud Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_ECODMS: {
      key: 'sync.interval.ecodms',
      category: 'sync',
      valueType: 'number',
      description: 'ecoDMS Sync-Intervall (Minuten)',
    },
    SYNC_INTERVAL_GEMBADOCS: {
      key: 'sync.interval.gembadocs',
      category: 'sync',
      valueType: 'number',
      description: 'GembaDocs Sync-Intervall (Minuten)',
    },
  };

  console.log('  Migrating system settings...');
  for (const [envKey, setting] of Object.entries(settingsMap)) {
    const envValue = process.env[envKey];
    if (envValue !== undefined) {
      await prisma.systemSetting.upsert({
        where: { key: setting.key },
        update: { value: envValue },
        create: {
          key: setting.key,
          value: envValue,
          category: setting.category,
          valueType: setting.valueType,
          description: setting.description,
        },
      });
      console.log(`    Migrated ${envKey} -> ${setting.key} = ${envValue}`);
    } else {
      console.log(`    Skipped  ${envKey} (not set in environment)`);
    }
  }

  // 2. Migrate integration credentials
  const encryptionKey = process.env.ENCRYPTION_KEY;
  if (!encryptionKey) {
    console.warn(
      '\n  WARNING: ENCRYPTION_KEY not set. Skipping credential migration.',
    );
    console.log('\nDone (settings only).');
    await prisma.$disconnect();
    return;
  }

  // Find admin user for createdById
  const adminUser = await prisma.user.findFirst({
    where: { roles: { some: { role: { name: 'admin' } } } },
  });

  if (!adminUser) {
    console.warn(
      '\n  WARNING: No admin user found. Skipping credential migration.',
    );
    console.log('\nDone (settings only).');
    await prisma.$disconnect();
    return;
  }

  const integrations: Array<{
    type: IntegrationType;
    name: string;
    envMap: Record<string, string>;
  }> = [
    {
      type: IntegrationType.PLENTYONE,
      name: 'PlentyONE Default',
      envMap: {
        baseUrl: 'PLENTYONE_BASE_URL',
        clientId: 'PLENTYONE_CLIENT_ID',
        clientSecret: 'PLENTYONE_CLIENT_SECRET',
      },
    },
    {
      type: IntegrationType.ZULIP,
      name: 'Zulip Default',
      envMap: {
        baseUrl: 'ZULIP_BASE_URL',
        email: 'ZULIP_EMAIL',
        apiKey: 'ZULIP_API_KEY',
      },
    },
    {
      type: IntegrationType.TODOIST,
      name: 'Todoist Default',
      envMap: {
        apiToken: 'TODOIST_API_TOKEN',
      },
    },
    {
      type: IntegrationType.FREESCOUT,
      name: 'FreeScout Default',
      envMap: {
        apiUrl: 'FREESCOUT_API_URL',
        apiKey: 'FREESCOUT_API_KEY',
      },
    },
    {
      type: IntegrationType.NEXTCLOUD,
      name: 'Nextcloud Default',
      envMap: {
        serverUrl: 'NEXTCLOUD_URL',
        username: 'NEXTCLOUD_USERNAME',
        password: 'NEXTCLOUD_PASSWORD',
      },
    },
    {
      type: IntegrationType.ECODMS,
      name: 'ecoDMS Default',
      envMap: {
        apiUrl: 'ECODMS_API_URL',
        username: 'ECODMS_USERNAME',
        password: 'ECODMS_PASSWORD',
      },
    },
    {
      type: IntegrationType.GEMBADOCS,
      name: 'GembaDocs Default',
      envMap: {
        apiUrl: 'GEMBADOCS_API_URL',
        apiKey: 'GEMBADOCS_API_KEY',
      },
    },
  ];

  console.log('\n  Migrating integration credentials...');

  for (const integration of integrations) {
    const creds: Record<string, string> = {};
    let hasValues = false;

    for (const [credKey, envKey] of Object.entries(integration.envMap)) {
      const val = process.env[envKey];
      if (val) {
        creds[credKey] = val;
        hasValues = true;
      }
    }

    if (hasValues) {
      const encrypted = encryptObject(creds, encryptionKey);

      const existing = await prisma.integrationCredential.findFirst({
        where: { type: integration.type, name: integration.name },
      });

      if (existing) {
        await prisma.integrationCredential.update({
          where: { id: existing.id },
          data: { credentials: encrypted },
        });
        console.log(`    Updated  ${integration.type} credentials`);
      } else {
        await prisma.integrationCredential.create({
          data: {
            type: integration.type,
            name: integration.name,
            credentials: encrypted,
            createdById: adminUser.id,
          },
        });
        console.log(`    Created  ${integration.type} credentials`);
      }
    } else {
      console.log(`    Skipped  ${integration.type} (no env vars set)`);
    }
  }

  console.log('\nMigration complete!');
  await prisma.$disconnect();
}

migrate().catch((error) => {
  console.error('Migration failed:', error);
  prisma.$disconnect();
  process.exit(1);
});
