/**
 * Datenbank-Restore Script
 *
 * Stellt Daten aus einem JSON-Backup wieder her.
 *
 * Verwendung:
 *   npm run db:restore                          # Letztes Backup
 *   npx tsx prisma/restore-data.ts <ordner>     # Bestimmtes Backup
 *
 * WICHTIG: Führe vorher 'npx prisma db push' oder 'npx prisma migrate deploy' aus,
 *          damit das Schema zur DB passt!
 */

import { PrismaClient, Prisma } from '@prisma/client';
import * as fs from 'fs';
import * as path from 'path';

const prisma = new PrismaClient();

// Hilfsfunktion: JSON-Datei lesen (leer bei fehlender Datei)
function readJsonFile<T>(filePath: string): T[] {
  if (!fs.existsSync(filePath)) return [];
  try {
    const content = fs.readFileSync(filePath, 'utf-8');
    return JSON.parse(content);
  } catch {
    return [];
  }
}

// Hilfsfunktion: ISO-Datum-Strings rekursiv zu Date-Objekten
function convertDates(obj: any): any {
  if (obj === null || obj === undefined) return obj;
  if (typeof obj === 'string') {
    if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(obj)) {
      return new Date(obj);
    }
    return obj;
  }
  if (Array.isArray(obj)) return obj.map(convertDates);
  if (typeof obj === 'object') {
    const result: any = {};
    for (const [key, value] of Object.entries(obj)) {
      result[key] = convertDates(value);
    }
    return result;
  }
  return obj;
}

/**
 * Generischer Restore-Helper: nutzt createMany mit skipDuplicates
 * wenn möglich, sonst einzelnes upsert per ID.
 */
async function restoreTable<T extends { id?: any }>(
  tableName: string,
  data: T[],
  model: any,
  options: { useCreateMany?: boolean; compositeKey?: string[] } = {},
): Promise<number> {
  if (data.length === 0) return 0;

  const converted = data.map(convertDates) as T[];

  // Bei einfachen Tabellen: createMany mit skipDuplicates
  if (options.useCreateMany) {
    try {
      const result = await model.createMany({
        data: converted,
        skipDuplicates: true,
      });
      return result.count;
    } catch (err: any) {
      // Fallback auf einzeln
    }
  }

  // Upsert per ID (oder Composite-Key)
  let count = 0;
  for (const item of converted) {
    try {
      if (options.compositeKey) {
        const where: any = {};
        const compositeWhere: any = {};
        for (const key of options.compositeKey) {
          compositeWhere[key] = (item as any)[key];
        }
        where[options.compositeKey.join('_')] = compositeWhere;
        await model.upsert({
          where,
          update: {},
          create: item,
        });
      } else {
        await model.upsert({
          where: { id: (item as any).id },
          update: item,
          create: item,
        });
      }
      count++;
    } catch (err: any) {
      console.log(`  ⚠️  Eintrag in ${tableName} (id=${(item as any).id}): ${err.message?.slice(0, 80)}`);
    }
  }
  return count;
}

async function main() {
  const backupsDir = path.join(__dirname, 'backups');
  let backupName = process.argv[2];

  if (!backupName) {
    if (!fs.existsSync(backupsDir)) {
      console.error('❌ Kein Backup-Ordner gefunden!');
      process.exit(1);
    }
    const backups = fs.readdirSync(backupsDir)
      .filter((f) => fs.statSync(path.join(backupsDir, f)).isDirectory())
      .sort()
      .reverse();

    if (backups.length === 0) {
      console.error('❌ Keine Backups gefunden!');
      process.exit(1);
    }
    backupName = backups[0];
    console.log(`📦 Verwende neuestes Backup: ${backupName}`);
  }

  const backupDir = path.join(backupsDir, backupName);
  if (!fs.existsSync(backupDir)) {
    console.error(`❌ Backup-Ordner nicht gefunden: ${backupDir}`);
    process.exit(1);
  }

  const infoPath = path.join(backupDir, '_backup-info.json');
  if (fs.existsSync(infoPath)) {
    const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8'));
    console.log(`\n📅 Backup vom: ${new Date(info.timestamp).toLocaleString('de-DE')}`);
    console.log(`📊 ${info.totalRecords} Datensätze\n`);
  }

  console.log(`🔄 Starte Wiederherstellung aus: ${backupDir}\n`);

  // Foreign Key Checks deaktivieren für MySQL
  await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 0');

  try {
    // Tabellen in Abhängigkeitsreihenfolge (gleich wie im Backup)
    const order: Array<{
      name: string;
      model: any;
      compositeKey?: string[];
    }> = [
      // Level 0
      { name: 'Permission', model: prisma.permission },
      { name: 'Role', model: prisma.role },
      { name: 'SalesPlatform', model: prisma.salesPlatform },
      { name: 'ContractCategory', model: prisma.contractCategory },
      { name: 'CancellationPeriod', model: prisma.cancellationPeriod },
      { name: 'ContractDuration', model: prisma.contractDuration },
      { name: 'AppSetting', model: prisma.appSetting },
      { name: 'EmailProviderConfig', model: prisma.emailProviderConfig },
      { name: 'Provider', model: prisma.provider },
      { name: 'PdfTemplate', model: prisma.pdfTemplate },
      { name: 'AuditRetentionPolicy', model: prisma.auditRetentionPolicy },

      // Level 1
      { name: 'RolePermission', model: prisma.rolePermission, compositeKey: ['roleId', 'permissionId'] },
      { name: 'User', model: prisma.user },
      { name: 'Customer', model: prisma.customer },
      { name: 'Tariff', model: prisma.tariff },

      // Level 2: Customer-abhängig
      { name: 'UserRole', model: prisma.userRole, compositeKey: ['userId', 'roleId'] },
      { name: 'Address', model: prisma.address },
      { name: 'BankCard', model: prisma.bankCard },
      { name: 'IdentityDocument', model: prisma.identityDocument },
      { name: 'Meter', model: prisma.meter },
      { name: 'StressfreiEmail', model: prisma.stressfreiEmail },
      { name: 'CustomerRepresentative', model: prisma.customerRepresentative },
      { name: 'CustomerConsent', model: prisma.customerConsent },
      { name: 'DataDeletionRequest', model: prisma.dataDeletionRequest },

      // Level 3: Contracts
      { name: 'Contract', model: prisma.contract },
      { name: 'RepresentativeAuthorization', model: prisma.representativeAuthorization },

      // Level 4: Vertragstyp-Details
      { name: 'EnergyContractDetails', model: prisma.energyContractDetails },
      { name: 'InternetContractDetails', model: prisma.internetContractDetails },
      { name: 'MobileContractDetails', model: prisma.mobileContractDetails },
      { name: 'TvContractDetails', model: prisma.tvContractDetails },
      { name: 'CarInsuranceDetails', model: prisma.carInsuranceDetails },
      { name: 'ContractMeter', model: prisma.contractMeter },
      { name: 'ContractDocument', model: prisma.contractDocument },
      { name: 'ContractHistoryEntry', model: prisma.contractHistoryEntry },
      { name: 'ContractTask', model: prisma.contractTask },
      { name: 'Invoice', model: prisma.invoice },
      { name: 'MeterReading', model: prisma.meterReading },

      // Level 5: Sub-Tabellen
      { name: 'ContractTaskSubtask', model: prisma.contractTaskSubtask },
      { name: 'PhoneNumber', model: prisma.phoneNumber },
      { name: 'SimCard', model: prisma.simCard },

      // Level 6: Logs & Emails
      { name: 'CachedEmail', model: prisma.cachedEmail },
      { name: 'EmailLog', model: prisma.emailLog },
      { name: 'AuditLog', model: prisma.auditLog },
    ];

    let totalRestored = 0;
    const skipped: string[] = [];

    for (const table of order) {
      const filePath = path.join(backupDir, `${table.name}.json`);
      const data = readJsonFile<any>(filePath);

      if (data.length === 0) {
        console.log(`⚪ ${table.name}: Keine Daten`);
        continue;
      }

      try {
        const count = await restoreTable(table.name, data, table.model, {
          compositeKey: table.compositeKey,
        });
        totalRestored += count;
        console.log(`✅ ${table.name}: ${count}/${data.length} Einträge wiederhergestellt`);
      } catch (error: any) {
        skipped.push(table.name);
        console.log(`⚠️  ${table.name}: Fehler - ${error.message?.slice(0, 100)}`);
      }
    }

    console.log(`\n✅ Wiederherstellung abgeschlossen!`);
    console.log(`   📊 ${totalRestored} Datensätze wiederhergestellt`);
    if (skipped.length > 0) {
      console.log(`   ⚠️  ${skipped.length} Tabellen mit Fehlern: ${skipped.join(', ')}`);
    }
    console.log('');
  } finally {
    // Foreign Key Checks wieder aktivieren
    await prisma.$executeRawUnsafe('SET FOREIGN_KEY_CHECKS = 1');
  }
}

main()
  .catch((e) => {
    console.error('❌ Wiederherstellung fehlgeschlagen:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
