/**
 * Factory-Defaults: Export + Import von Stammdaten-Katalogen.
 * Enthält KEINE Kundendaten, Verträge, Dokumente oder E-Mails –
 * nur reine Kataloge: Anbieter, Tarife, Kündigungsfristen, Laufzeiten,
 * Vertragskategorien, PDF-Auftragsvorlagen und ausgewählte
 * HTML-Templates (Datenschutz / Impressum / Vollmacht).
 */

import fs from 'fs';
import path from 'path';
import archiver from 'archiver';
import AdmZip from 'adm-zip';
import prisma from '../lib/prisma.js';

// Whitelist der AppSetting-Keys, die ins Factory-Default-Bundle gehören.
// Bewusst klein gehalten: nur HTML-Templates für rechtliche Standardtexte –
// keine Secrets, keine SMTP-Konfiguration, keine User-spezifischen Settings.
export const FACTORY_DEFAULT_APP_SETTING_KEYS = [
  'privacyPolicyHtml',
  'authorizationTemplateHtml',
  'imprintHtml',
  'websitePrivacyPolicyHtml',
] as const;

export interface AppSettingExport {
  key: (typeof FACTORY_DEFAULT_APP_SETTING_KEYS)[number];
  value: string;
}

export interface FactoryDefaultsManifest {
  version: 1;
  exportedAt: string;
  counts: {
    providers: number;
    tariffs: number;
    cancellationPeriods: number;
    contractDurations: number;
    contractCategories: number;
    pdfTemplates: number;
    appSettings: number;
  };
}

export interface ProviderExport {
  name: string;
  portalUrl: string | null;
  usernameFieldName: string | null;
  passwordFieldName: string | null;
  contactEmail: string | null;
  contactPhone: string | null;
  contactFax: string | null;
  contactAddress: string | null;
  cancellationEmail: string | null;
  cancellationFax: string | null;
  cancellationAddress: string | null;
  isActive: boolean;
  tariffs: { name: string; isActive: boolean }[];
}

export interface PdfTemplateExport {
  name: string;
  description: string | null;
  providerName: string | null;
  originalName: string;
  fieldMapping: any;
  phoneFieldPrefix: string | null;
  maxPhoneFields: number | null;
  isActive: boolean;
  // Datei wird separat im ZIP abgelegt unter pdf-templates/<name>.pdf
  pdfFilename: string;
}

/**
 * Sammelt alle Katalog-Daten aus der DB.
 */
export async function collectFactoryDefaults() {
  const [providers, cancellationPeriods, contractDurations, contractCategories, pdfTemplates, appSettings] =
    await Promise.all([
      prisma.provider.findMany({
        include: { tariffs: { select: { name: true, isActive: true } } },
        orderBy: { name: 'asc' },
      }),
      prisma.cancellationPeriod.findMany({ orderBy: { code: 'asc' } }),
      prisma.contractDuration.findMany({ orderBy: { code: 'asc' } }),
      prisma.contractCategory.findMany({ orderBy: { sortOrder: 'asc' } }),
      prisma.pdfTemplate.findMany({ orderBy: { name: 'asc' } }),
      prisma.appSetting.findMany({
        where: { key: { in: [...FACTORY_DEFAULT_APP_SETTING_KEYS] } },
        select: { key: true, value: true },
        orderBy: { key: 'asc' },
      }),
    ]);

  return {
    providers: providers.map<ProviderExport>((p) => ({
      name: p.name,
      portalUrl: p.portalUrl,
      usernameFieldName: p.usernameFieldName,
      passwordFieldName: p.passwordFieldName,
      contactEmail: p.contactEmail,
      contactPhone: p.contactPhone,
      contactFax: p.contactFax,
      contactAddress: p.contactAddress,
      cancellationEmail: p.cancellationEmail,
      cancellationFax: p.cancellationFax,
      cancellationAddress: p.cancellationAddress,
      isActive: p.isActive,
      tariffs: p.tariffs.map((t) => ({ name: t.name, isActive: t.isActive })),
    })),
    cancellationPeriods: cancellationPeriods.map((c) => ({
      code: c.code,
      description: c.description,
      isActive: c.isActive,
    })),
    contractDurations: contractDurations.map((d) => ({
      code: d.code,
      description: d.description,
      isActive: d.isActive,
    })),
    contractCategories: contractCategories.map((c) => ({
      code: c.code,
      name: c.name,
      icon: c.icon,
      color: c.color,
      sortOrder: c.sortOrder,
      isActive: c.isActive,
    })),
    pdfTemplates: pdfTemplates.map<PdfTemplateExport>((t) => {
      // fieldMapping ist im DB als String abgelegt – zu Objekt parsen
      let fieldMapping: any = {};
      try {
        fieldMapping = JSON.parse(t.fieldMapping);
      } catch {
        fieldMapping = {};
      }
      return {
        name: t.name,
        description: t.description,
        providerName: t.providerName,
        originalName: t.originalName,
        fieldMapping,
        phoneFieldPrefix: t.phoneFieldPrefix,
        maxPhoneFields: t.maxPhoneFields,
        isActive: t.isActive,
        pdfFilename: sanitizeFilename(t.name) + path.extname(t.originalName || '.pdf'),
      };
    }),
    appSettings: appSettings as AppSettingExport[],
  };
}

function sanitizeFilename(name: string): string {
  return name.replace(/[^a-zA-Z0-9äöüÄÖÜß_-]/g, '_').replace(/_+/g, '_');
}

/**
 * Erstellt ein ZIP-Archiv mit allen Factory-Defaults.
 * Die PDF-Dateien werden aus dem uploads-Verzeichnis gelesen.
 */
export async function exportFactoryDefaults(): Promise<Buffer> {
  const data = await collectFactoryDefaults();

  const manifest: FactoryDefaultsManifest = {
    version: 1,
    exportedAt: new Date().toISOString(),
    counts: {
      providers: data.providers.length,
      tariffs: data.providers.reduce((sum, p) => sum + p.tariffs.length, 0),
      cancellationPeriods: data.cancellationPeriods.length,
      contractDurations: data.contractDurations.length,
      contractCategories: data.contractCategories.length,
      pdfTemplates: data.pdfTemplates.length,
      appSettings: data.appSettings.length,
    },
  };

  return new Promise<Buffer>((resolve, reject) => {
    const archive = archiver('zip', { zlib: { level: 9 } });
    const chunks: Buffer[] = [];

    archive.on('data', (chunk: Buffer) => chunks.push(chunk));
    archive.on('end', () => resolve(Buffer.concat(chunks)));
    archive.on('error', reject);

    // JSON-Dateien vorbereiten
    archive.append(JSON.stringify(manifest, null, 2), { name: 'manifest.json' });
    archive.append(JSON.stringify(data.providers, null, 2), {
      name: 'providers/providers.json',
    });
    archive.append(JSON.stringify(data.cancellationPeriods, null, 2), {
      name: 'contract-meta/cancellation-periods.json',
    });
    archive.append(JSON.stringify(data.contractDurations, null, 2), {
      name: 'contract-meta/contract-durations.json',
    });
    archive.append(JSON.stringify(data.contractCategories, null, 2), {
      name: 'contract-meta/contract-categories.json',
    });
    archive.append(JSON.stringify(data.pdfTemplates, null, 2), {
      name: 'pdf-templates/pdf-templates.json',
    });
    archive.append(JSON.stringify(data.appSettings, null, 2), {
      name: 'app-settings/app-settings.json',
    });

    // PDF-Dateien physisch hinzufügen (Pfade aus DB laden)
    const uploadsRoot = path.join(process.cwd(), 'uploads');
    (async () => {
      try {
        const templateRows = await prisma.pdfTemplate.findMany({
          select: { name: true, templatePath: true },
        });
        const pathByName = new Map(templateRows.map((r) => [r.name, r.templatePath]));

        for (const t of data.pdfTemplates) {
          const storedPath = pathByName.get(t.name);
          if (!storedPath) continue;

          const relPath = storedPath.startsWith('/uploads/')
            ? storedPath.substring('/uploads/'.length)
            : storedPath;
          const absPath = path.join(uploadsRoot, relPath);

          if (fs.existsSync(absPath)) {
            archive.file(absPath, { name: `pdf-templates/${t.pdfFilename}` });
          } else {
            console.warn(`[factoryDefaults] PDF-Datei nicht gefunden: ${absPath}`);
          }
        }
        archive.finalize();
      } catch (err) {
        reject(err);
      }
    })();
  });
}

// ============================================================
// IMPORT
// ============================================================

export interface FactoryDefaultsImportResult {
  providers: number;
  tariffs: number;
  cancellationPeriods: number;
  contractDurations: number;
  contractCategories: number;
  pdfTemplates: number;
  pdfTemplatesSkipped: number;
  appSettings: number;
  warnings: string[];
}

function parseJsonEntry<T>(zip: AdmZip, name: string): T[] {
  const entry = zip.getEntry(name);
  if (!entry) return [];
  try {
    const parsed = JSON.parse(entry.getData().toString('utf-8'));
    return Array.isArray(parsed) ? parsed : [];
  } catch {
    return [];
  }
}

/**
 * Wendet ein Factory-Defaults-ZIP idempotent auf die DB an.
 * - upsert über unique-Keys: nichts wird gelöscht
 * - PDFs landen in `${uploads}/pdf-templates/` mit eindeutigem Suffix
 * - AppSettings nur Whitelist-Keys (FACTORY_DEFAULT_APP_SETTING_KEYS)
 *
 * Robust gegen Zip-Slip: wir greifen nur auf bekannte Entry-Namen zu
 * (`pdf-templates/<basename>`), niemals auf einen aus dem ZIP konstruierten
 * Pfad im Filesystem.
 */
export async function importFactoryDefaults(
  zipBuffer: Buffer,
): Promise<FactoryDefaultsImportResult> {
  const zip = new AdmZip(zipBuffer);
  const result: FactoryDefaultsImportResult = {
    providers: 0,
    tariffs: 0,
    cancellationPeriods: 0,
    contractDurations: 0,
    contractCategories: 0,
    pdfTemplates: 0,
    pdfTemplatesSkipped: 0,
    appSettings: 0,
    warnings: [],
  };

  // --- Providers + Tariffs
  const providers = parseJsonEntry<ProviderExport>(zip, 'providers/providers.json');
  for (const p of providers) {
    if (!p.name) continue;
    const provider = await prisma.provider.upsert({
      where: { name: p.name },
      update: {
        portalUrl: p.portalUrl ?? null,
        usernameFieldName: p.usernameFieldName ?? null,
        passwordFieldName: p.passwordFieldName ?? null,
        contactEmail: p.contactEmail ?? null,
        contactPhone: p.contactPhone ?? null,
        contactFax: p.contactFax ?? null,
        contactAddress: p.contactAddress ?? null,
        cancellationEmail: p.cancellationEmail ?? null,
        cancellationFax: p.cancellationFax ?? null,
        cancellationAddress: p.cancellationAddress ?? null,
        isActive: p.isActive ?? true,
      },
      create: {
        name: p.name,
        portalUrl: p.portalUrl ?? null,
        usernameFieldName: p.usernameFieldName ?? null,
        passwordFieldName: p.passwordFieldName ?? null,
        contactEmail: p.contactEmail ?? null,
        contactPhone: p.contactPhone ?? null,
        contactFax: p.contactFax ?? null,
        contactAddress: p.contactAddress ?? null,
        cancellationEmail: p.cancellationEmail ?? null,
        cancellationFax: p.cancellationFax ?? null,
        cancellationAddress: p.cancellationAddress ?? null,
        isActive: p.isActive ?? true,
      },
    });
    result.providers++;
    for (const t of p.tariffs ?? []) {
      if (!t.name) continue;
      await prisma.tariff.upsert({
        where: { providerId_name: { providerId: provider.id, name: t.name } },
        update: { isActive: t.isActive ?? true },
        create: { providerId: provider.id, name: t.name, isActive: t.isActive ?? true },
      });
      result.tariffs++;
    }
  }

  // --- Contract-Meta
  const cancellationPeriods = parseJsonEntry<{ code: string; description: string; isActive?: boolean }>(
    zip,
    'contract-meta/cancellation-periods.json',
  );
  for (const c of cancellationPeriods) {
    if (!c.code || !c.description) continue;
    await prisma.cancellationPeriod.upsert({
      where: { code: c.code },
      update: { description: c.description, isActive: c.isActive ?? true },
      create: { code: c.code, description: c.description, isActive: c.isActive ?? true },
    });
    result.cancellationPeriods++;
  }

  const contractDurations = parseJsonEntry<{ code: string; description: string; isActive?: boolean }>(
    zip,
    'contract-meta/contract-durations.json',
  );
  for (const d of contractDurations) {
    if (!d.code || !d.description) continue;
    await prisma.contractDuration.upsert({
      where: { code: d.code },
      update: { description: d.description, isActive: d.isActive ?? true },
      create: { code: d.code, description: d.description, isActive: d.isActive ?? true },
    });
    result.contractDurations++;
  }

  const contractCategories = parseJsonEntry<{
    code: string;
    name: string;
    icon?: string | null;
    color?: string | null;
    sortOrder?: number;
    isActive?: boolean;
  }>(zip, 'contract-meta/contract-categories.json');
  for (const c of contractCategories) {
    if (!c.code || !c.name) continue;
    await prisma.contractCategory.upsert({
      where: { code: c.code },
      update: {
        name: c.name,
        icon: c.icon ?? null,
        color: c.color ?? null,
        sortOrder: c.sortOrder ?? 0,
        isActive: c.isActive ?? true,
      },
      create: {
        code: c.code,
        name: c.name,
        icon: c.icon ?? null,
        color: c.color ?? null,
        sortOrder: c.sortOrder ?? 0,
        isActive: c.isActive ?? true,
      },
    });
    result.contractCategories++;
  }

  // --- PDF-Vorlagen (JSON + binär aus dem ZIP)
  const pdfTemplates = parseJsonEntry<PdfTemplateExport>(
    zip,
    'pdf-templates/pdf-templates.json',
  );
  if (pdfTemplates.length > 0) {
    const uploadsRoot = path.join(process.cwd(), 'uploads');
    const pdfDestDir = path.join(uploadsRoot, 'pdf-templates');
    if (!fs.existsSync(pdfDestDir)) {
      fs.mkdirSync(pdfDestDir, { recursive: true });
    }
    for (const t of pdfTemplates) {
      if (!t.name || !t.pdfFilename) continue;
      // Anti-Zip-Slip: nur basename verwenden, kein Pfad
      const basename = path.basename(t.pdfFilename);
      const entry = zip.getEntry(`pdf-templates/${basename}`);
      if (!entry) {
        result.pdfTemplatesSkipped++;
        result.warnings.push(`PDF fehlt im ZIP: ${basename} – Vorlage "${t.name}" übersprungen`);
        continue;
      }

      const ext = path.extname(t.originalName || basename) || '.pdf';
      const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
      const safeName = t.name.replace(/[^a-zA-Z0-9]/g, '-');
      const destFilename = `seed-${safeName}-${uniqueSuffix}${ext}`;
      const destPdf = path.join(pdfDestDir, destFilename);
      const relativePath = `/uploads/pdf-templates/${destFilename}`;

      fs.writeFileSync(destPdf, entry.getData());

      // Bei existierender Vorlage die alte Datei aufräumen
      const existing = await prisma.pdfTemplate.findUnique({ where: { name: t.name } });
      if (existing?.templatePath) {
        const oldRel = existing.templatePath.startsWith('/uploads/')
          ? existing.templatePath.substring('/uploads/'.length)
          : existing.templatePath;
        const oldAbs = path.join(uploadsRoot, oldRel);
        if (fs.existsSync(oldAbs)) {
          try {
            fs.unlinkSync(oldAbs);
          } catch {
            // ignore
          }
        }
      }

      const fieldMappingJson = JSON.stringify(t.fieldMapping ?? {});
      await prisma.pdfTemplate.upsert({
        where: { name: t.name },
        update: {
          description: t.description ?? null,
          providerName: t.providerName ?? null,
          templatePath: relativePath,
          originalName: t.originalName,
          fieldMapping: fieldMappingJson,
          phoneFieldPrefix: t.phoneFieldPrefix ?? null,
          maxPhoneFields: t.maxPhoneFields ?? 8,
          isActive: t.isActive ?? true,
        },
        create: {
          name: t.name,
          description: t.description ?? null,
          providerName: t.providerName ?? null,
          templatePath: relativePath,
          originalName: t.originalName,
          fieldMapping: fieldMappingJson,
          phoneFieldPrefix: t.phoneFieldPrefix ?? null,
          maxPhoneFields: t.maxPhoneFields ?? 8,
          isActive: t.isActive ?? true,
        },
      });
      result.pdfTemplates++;
    }
  }

  // --- AppSettings (HTML-Templates, Whitelist)
  const appSettings = parseJsonEntry<AppSettingExport>(zip, 'app-settings/app-settings.json');
  const allowedKeys = new Set<string>(FACTORY_DEFAULT_APP_SETTING_KEYS);
  for (const s of appSettings) {
    if (!s.key || typeof s.value !== 'string') continue;
    if (!allowedKeys.has(s.key)) {
      result.warnings.push(`AppSetting-Key '${s.key}' nicht auf Whitelist – ignoriert`);
      continue;
    }
    await prisma.appSetting.upsert({
      where: { key: s.key },
      update: { value: s.value },
      create: { key: s.key, value: s.value },
    });
    result.appSettings++;
  }

  return result;
}
