import { ConsentType, ConsentStatus } from '@prisma/client';
import crypto from 'crypto';
import prisma from '../lib/prisma.js';
import * as consentService from './consent.service.js';
import * as appSettingService from './appSetting.service.js';
import PDFDocument from 'pdfkit';

// Pentest 57.7 (MEDIUM, 2026-06-01): Public-Consent-Hashes hatten keine
// Ablauffrist. Ein versehentlich weitergegebener oder geleakter Link
// hätte Jahre später noch fremde Einwilligungen erteilen können
// (DSGVO-Pflicht zur Zweckbindung). 30 Tage ist der Default-Zeitraum,
// in dem ein Kunde realistisch auf den Versandlink klickt; danach muss
// ein Mitarbeiter den Link neu generieren (ensureConsentHash() erzeugt
// einen neuen Hash + neue Frist).
const CONSENT_HASH_TTL_DAYS = 30;

/**
 * Kunden-Lookup per consentHash. Liefert null wenn der Hash unbekannt
 * oder abgelaufen ist – aus Sicht des Aufrufers identisch, damit der
 * Public-Endpoint keine Unterscheidung "ungültig vs. abgelaufen" leakt.
 */
export async function getCustomerByConsentHash(hash: string) {
  const customer = await prisma.customer.findUnique({
    where: { consentHash: hash },
    select: {
      id: true,
      firstName: true,
      lastName: true,
      customerNumber: true,
      salutation: true,
      email: true,
      consentHashExpiresAt: true,
    },
  });

  if (!customer) return null;
  if (customer.consentHashExpiresAt && customer.consentHashExpiresAt.getTime() < Date.now()) {
    return null;
  }

  const consents = await consentService.getCustomerConsents(customer.id);

  // consentHashExpiresAt nicht an den Client durchreichen
  const { consentHashExpiresAt: _expires, ...customerWithoutExpiry } = customer;
  void _expires;
  return { customer: customerWithoutExpiry, consents };
}

/**
 * Alle 4 Einwilligungen über den öffentlichen Link erteilen.
 * Wirft bei abgelaufenem oder unbekanntem Hash mit gleicher Meldung,
 * damit kein Oracle "existiert vs. abgelaufen" entsteht.
 */
export async function grantAllConsentsPublic(hash: string, ipAddress: string) {
  const customer = await prisma.customer.findUnique({
    where: { consentHash: hash },
    select: { id: true, firstName: true, lastName: true, consentHashExpiresAt: true },
  });

  if (!customer) {
    throw new Error('Ungültiger Link');
  }
  if (customer.consentHashExpiresAt && customer.consentHashExpiresAt.getTime() < Date.now()) {
    throw new Error('Link ist abgelaufen. Bitte einen neuen Link anfordern.');
  }

  const results = [];
  for (const type of Object.values(ConsentType)) {
    const result = await consentService.updateConsent(customer.id, type, {
      status: ConsentStatus.GRANTED,
      source: 'public-link',
      ipAddress,
      createdBy: `${customer.firstName} ${customer.lastName} (Public-Link)`,
    });
    results.push(result);
  }

  return results;
}

/**
 * consentHash generieren oder erneuern. Liefert einen bestehenden Hash
 * nur zurück, wenn dessen TTL noch nicht abgelaufen ist – sonst wird ein
 * neuer Hash + neue Frist gesetzt (Pentest 57.7).
 */
export async function ensureConsentHash(customerId: number): Promise<string> {
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: { consentHash: true, consentHashExpiresAt: true },
  });

  if (!customer) {
    throw new Error('Kunde nicht gefunden');
  }

  const stillValid = customer.consentHash
    && customer.consentHashExpiresAt
    && customer.consentHashExpiresAt.getTime() > Date.now();

  if (stillValid) {
    return customer.consentHash!;
  }

  const hash = crypto.randomUUID();
  const expiresAt = new Date(Date.now() + CONSENT_HASH_TTL_DAYS * 24 * 60 * 60 * 1000);
  await prisma.customer.update({
    where: { id: customerId },
    data: { consentHash: hash, consentHashExpiresAt: expiresAt },
  });

  return hash;
}

/**
 * Platzhalter in Text ersetzen
 */
function replacePlaceholders(html: string, customer: {
  firstName: string;
  lastName: string;
  customerNumber: string;
  salutation?: string | null;
  email?: string | null;
}): string {
  return html
    .replace(/\{\{vorname\}\}/gi, customer.firstName || '')
    .replace(/\{\{nachname\}\}/gi, customer.lastName || '')
    .replace(/\{\{kundennummer\}\}/gi, customer.customerNumber || '')
    .replace(/\{\{anrede\}\}/gi, customer.salutation || '')
    .replace(/\{\{email\}\}/gi, customer.email || '')
    .replace(/\{\{datum\}\}/gi, new Date().toLocaleDateString('de-DE', {
      day: '2-digit',
      month: '2-digit',
      year: 'numeric',
    }));
}

/**
 * Datenschutzerklärung als HTML abrufen (mit Platzhaltern ersetzt)
 */
export async function getPrivacyPolicyHtml(customerId?: number): Promise<string> {
  const html = await appSettingService.getSetting('privacyPolicyHtml');

  if (!html) {
    return '<p>Keine Datenschutzerklärung hinterlegt.</p>';
  }

  if (!customerId) return html;

  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: {
      firstName: true,
      lastName: true,
      customerNumber: true,
      salutation: true,
      email: true,
    },
  });

  if (!customer) return html;

  return replacePlaceholders(html, customer);
}

/**
 * HTML zu Plain-Text konvertieren (für PDF)
 */
function htmlToText(html: string): string {
  return html
    .replace(/<h[1-6][^>]*>(.*?)<\/h[1-6]>/gi, '\n$1\n')
    .replace(/<br\s*\/?>/gi, '\n')
    .replace(/<\/p>/gi, '\n\n')
    .replace(/<li[^>]*>(.*?)<\/li>/gi, '  • $1\n')
    .replace(/<[^>]+>/g, '')
    .replace(/&amp;/g, '&')
    .replace(/&lt;/g, '<')
    .replace(/&gt;/g, '>')
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/&nbsp;/g, ' ')
    .replace(/\n{3,}/g, '\n\n')
    .trim();
}

/**
 * Datenschutzerklärung als PDF generieren
 */
export async function generateConsentPdf(customerId: number): Promise<Buffer> {
  const html = await getPrivacyPolicyHtml(customerId);
  const text = htmlToText(html);

  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({ size: 'A4', margin: 50 });
    const chunks: Buffer[] = [];

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

    // Titel
    doc.fontSize(18).font('Helvetica-Bold').text('Datenschutzerklärung', { align: 'center' });
    doc.moveDown(1);

    // Datum
    doc.fontSize(10).font('Helvetica')
      .text(`Erstellt am: ${new Date().toLocaleDateString('de-DE')}`, { align: 'right' });
    doc.moveDown(1);

    // Inhalt
    doc.fontSize(11).font('Helvetica').text(text, {
      align: 'left',
      lineGap: 4,
    });

    doc.end();
  });
}

/**
 * Datenschutzerklärung als unterschreibbare PDF (Papierform) generieren.
 * Zusätzlich zum normalen Text wird unten eine Einwilligungs-Klausel +
 * ein Unterschriften-Block angefügt (Ort/Datum + Unterschrift +
 * Name in Druckbuchstaben). Das fertige PDF wird ausgedruckt, vom
 * Kunden unterschrieben und im Tab "Einwilligungen / Datenschutz"
 * wieder hochgeladen.
 */
export async function generateSignablePrivacyPdf(customerId: number): Promise<Buffer> {
  const html = await getPrivacyPolicyHtml(customerId);
  const text = htmlToText(html);
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: {
      firstName: true, lastName: true, customerNumber: true, companyName: true,
      salutation: true,
    },
  });
  const printedName = customer
    ? (customer.companyName?.trim()
        ? customer.companyName.trim()
        : `${customer.firstName ?? ''} ${customer.lastName ?? ''}`.trim())
    : '';

  return new Promise((resolve, reject) => {
    const doc = new PDFDocument({ size: 'A4', margin: 50 });
    const chunks: Buffer[] = [];

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

    // Titel
    doc.fontSize(18).font('Helvetica-Bold').text('Datenschutzerklärung', { align: 'center' });
    doc.moveDown(0.5);

    // Kundenkopf
    if (printedName) {
      doc.fontSize(11).font('Helvetica-Bold').text(printedName, { align: 'center' });
    }
    if (customer?.customerNumber) {
      doc.fontSize(10).font('Helvetica').text(`Kundennummer: ${customer.customerNumber}`, { align: 'center' });
    }
    doc.moveDown(0.5);
    doc.fontSize(10).font('Helvetica')
      .text(`Erstellt am: ${new Date().toLocaleDateString('de-DE')}`, { align: 'right' });
    doc.moveDown(1);

    // Inhalt
    doc.fontSize(11).font('Helvetica').text(text, { align: 'left', lineGap: 4 });

    // Genug Platz vor dem Unterschriftenblock – wenn nicht mehr genug
    // Platz auf der Seite, neue Seite anfangen.
    if (doc.y > doc.page.height - doc.page.margins.bottom - 220) {
      doc.addPage();
    } else {
      doc.moveDown(2);
    }

    // Einwilligungsklausel
    doc.fontSize(11).font('Helvetica-Bold').text('Einwilligung', { underline: false });
    doc.moveDown(0.3);
    doc.fontSize(10).font('Helvetica').text(
      'Mit meiner Unterschrift bestätige ich, dass ich die vorstehende ' +
      'Datenschutzerklärung gelesen und verstanden habe und mit der ' +
      'Verarbeitung meiner personenbezogenen Daten zum Zweck der ' +
      'Vertragserfüllung einverstanden bin. Diese Einwilligung kann ' +
      'jederzeit für die Zukunft widerrufen werden.',
      { align: 'left', lineGap: 3 },
    );
    doc.moveDown(1.5);

    // Unterschriftenblock: links Ort/Datum, rechts Unterschrift
    const startY = doc.y;
    const leftX = doc.page.margins.left;
    const rightX = doc.page.width / 2 + 10;
    const lineWidth = doc.page.width / 2 - doc.page.margins.left - 10;

    // Linien
    const lineY = startY + 35;
    doc.moveTo(leftX, lineY).lineTo(leftX + lineWidth, lineY).stroke();
    doc.moveTo(rightX, lineY).lineTo(rightX + lineWidth, lineY).stroke();

    // Labels unter den Linien
    doc.fontSize(9).font('Helvetica');
    doc.text('Ort, Datum', leftX, lineY + 4, { width: lineWidth, align: 'left' });
    doc.text('Unterschrift', rightX, lineY + 4, { width: lineWidth, align: 'left' });

    // Zweite Zeile: Name in Druckbuchstaben (vorausgefüllt mit Kunde)
    doc.moveDown(3);
    const nameY = doc.y;
    doc.fontSize(11).font('Helvetica');
    if (printedName) {
      doc.text(printedName, rightX, nameY, { width: lineWidth, align: 'left' });
    }
    doc.moveTo(rightX, nameY + 16).lineTo(rightX + lineWidth, nameY + 16).stroke();
    doc.fontSize(9).text('Name in Druckbuchstaben', rightX, nameY + 20, { width: lineWidth, align: 'left' });

    doc.end();
  });
}
