import { ConsentType, ConsentStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import fs from 'fs';
import path from 'path';
import { assertValidDocumentPath } from '../utils/sanitize.js';

// Whitelist legitimer Werte für CustomerConsent.source. Schema-Kommentar:
// "portal", "telefon", "papier", "email". Public-Link-Flow nutzt
// 'public-link', CRM-Backend-Override 'crm-backend'. Alles andere
// (z.B. "ADMIN_OVERRIDE", "<script>") wird abgelehnt – Pentest 2026-05-20.
export const ALLOWED_CONSENT_SOURCES: ReadonlySet<string> = new Set([
  'portal',
  'public-link',
  'telefon',
  'papier',
  'email',
  'crm-backend',
]);

export function sanitizeConsentSource(value: unknown, fallback: string): string {
  const v = typeof value === 'string' ? value : '';
  return ALLOWED_CONSENT_SOURCES.has(v) ? v : fallback;
}

export interface UpdateConsentData {
  status: ConsentStatus;
  source?: string;
  documentPath?: string;
  version?: string;
  ipAddress?: string;
  createdBy: string;
}

/**
 * Holt alle Einwilligungen eines Kunden
 */
export async function getCustomerConsents(customerId: number) {
  const consents = await prisma.customerConsent.findMany({
    where: { customerId },
    orderBy: { consentType: 'asc' },
  });

  // Alle verfügbaren Consent-Typen mit Status
  const allTypes = Object.values(ConsentType);
  const consentMap = new Map(consents.map((c) => [c.consentType, c]));

  return allTypes.map((type) => {
    const existing = consentMap.get(type);
    return existing || {
      id: null,
      customerId,
      consentType: type,
      status: 'PENDING' as ConsentStatus,
      grantedAt: null,
      withdrawnAt: null,
      source: null,
      documentPath: null,
      version: null,
      ipAddress: null,
      createdBy: null,
      createdAt: null,
      updatedAt: null,
    };
  });
}

/**
 * Aktualisiert oder erstellt eine Einwilligung
 */
export async function updateConsent(
  customerId: number,
  consentType: ConsentType,
  data: UpdateConsentData
) {
  // Prüfen ob Kunde existiert
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
  });

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

  // Pentest 26.7: documentPath darf nur ein gültiger /uploads/<safe>-Pfad
  // sein. Aktuell hat KEIN Endpoint diesen Wert aus User-Input gemappt
  // (Portal: nicht aus Body, Admin-Auth-Upload: server-generated). Diese
  // Service-Side-Validation ist Defense-in-Depth gegen zukünftige Caller.
  assertValidDocumentPath(data.documentPath, 'documentPath');

  const now = new Date();
  const updateData = {
    status: data.status,
    source: data.source,
    documentPath: data.documentPath,
    version: data.version,
    ipAddress: data.ipAddress,
    grantedAt: data.status === 'GRANTED' ? now : undefined,
    withdrawnAt: data.status === 'WITHDRAWN' ? now : undefined,
  };

  const result = await prisma.customerConsent.upsert({
    where: {
      customerId_consentType: { customerId, consentType },
    },
    update: updateData,
    create: {
      customerId,
      consentType,
      ...updateData,
      createdBy: data.createdBy,
    },
  });

  // Bei Widerruf: Datenschutz-PDF löschen wenn keine Einwilligung mehr besteht
  if (data.status === 'WITHDRAWN') {
    await deletePrivacyPdfOnWithdraw(customerId);
  }

  return result;
}

/**
 * Holt die Historie einer Einwilligung (aus Audit-Logs)
 */
export async function getConsentHistory(customerId: number, consentType: ConsentType) {
  // Aus Audit-Logs die Änderungen dieser Einwilligung abrufen
  const logs = await prisma.auditLog.findMany({
    where: {
      resourceType: 'CustomerConsent',
      dataSubjectId: customerId,
      changesAfter: { contains: consentType },
    },
    orderBy: { createdAt: 'desc' },
    take: 50,
  });

  return logs;
}

/**
 * Prüft ob eine bestimmte Einwilligung erteilt wurde
 */
export async function hasConsent(customerId: number, consentType: ConsentType): Promise<boolean> {
  const consent = await prisma.customerConsent.findUnique({
    where: {
      customerId_consentType: { customerId, consentType },
    },
  });

  return consent?.status === 'GRANTED';
}

/**
 * Prüft ob ein Kunde die DSGVO-Einwilligung erfüllt hat.
 * Erfüllt = entweder privacyPolicyPath vorhanden ODER alle Online-Consents GRANTED.
 */
export async function hasFullConsent(customerId: number): Promise<{
  hasConsent: boolean;
  hasPaperConsent: boolean;
  hasOnlineConsent: boolean;
  consentDetails: { type: string; status: string }[];
  consentHash: string | null;
}> {
  // Prüfe ob Papier-Datenschutzerklärung vorhanden
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: { privacyPolicyPath: true, consentHash: true },
  });

  const hasPaperConsent = !!customer?.privacyPolicyPath;

  // Online-Consents prüfen
  const allTypes = Object.values(ConsentType);
  const consents = await prisma.customerConsent.findMany({
    where: { customerId },
  });

  const consentMap = new Map(consents.map((c) => [c.consentType, c.status]));
  const consentDetails = allTypes.map((type) => ({
    type,
    status: (consentMap.get(type) || 'PENDING') as string,
  }));

  const hasOnlineConsent = allTypes.every(
    (type) => consentMap.get(type) === 'GRANTED'
  );

  return {
    hasConsent: hasPaperConsent || hasOnlineConsent,
    hasPaperConsent,
    hasOnlineConsent,
    consentDetails,
    consentHash: customer?.consentHash || null,
  };
}

/**
 * Widerruft alle Einwilligungen eines Kunden
 */
export async function withdrawAllConsents(customerId: number, withdrawnBy: string) {
  const result = await prisma.customerConsent.updateMany({
    where: {
      customerId,
      status: 'GRANTED',
    },
    data: {
      status: 'WITHDRAWN',
      withdrawnAt: new Date(),
    },
  });

  // Datenschutz-PDF löschen
  await deletePrivacyPdfOnWithdraw(customerId);

  return result;
}

/**
 * Löscht die Datenschutz-PDF bei Widerruf.
 * Sobald auch nur eine Einwilligung widerrufen wird, ist die Gesamteinwilligung ungültig.
 */
async function deletePrivacyPdfOnWithdraw(customerId: number) {
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: { privacyPolicyPath: true },
  });

  if (customer?.privacyPolicyPath) {
    try {
      const filePath = path.join(process.cwd(), customer.privacyPolicyPath);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    } catch (err) {
      console.error('Fehler beim Löschen der Datenschutz-PDF:', err);
    }

    await prisma.customer.update({
      where: { id: customerId },
      data: { privacyPolicyPath: null },
    });

    console.log(`Datenschutz-PDF für Kunde ${customerId} gelöscht (Einwilligung widerrufen)`);
  }
}

/**
 * Consent-Übersicht für DSGVO-Dashboard
 */
export async function getConsentOverview() {
  const allConsents = await prisma.customerConsent.groupBy({
    by: ['consentType', 'status'],
    _count: { id: true },
  });

  // Gruppieren nach Typ
  const overview: Record<string, { granted: number; withdrawn: number; pending: number }> = {};

  for (const type of Object.values(ConsentType)) {
    overview[type] = { granted: 0, withdrawn: 0, pending: 0 };
  }

  for (const row of allConsents) {
    const type = row.consentType;
    const status = row.status.toLowerCase() as 'granted' | 'withdrawn' | 'pending';
    overview[type][status] = row._count.id;
  }

  return overview;
}

/**
 * Consent-Typ Labels für UI
 */
export const CONSENT_TYPE_LABELS: Record<ConsentType, { label: string; description: string }> = {
  DATA_PROCESSING: {
    label: 'Datenverarbeitung',
    description: 'Grundlegende Verarbeitung personenbezogener Daten zur Vertragserfüllung',
  },
  MARKETING_EMAIL: {
    label: 'Elektronisches Marketing',
    description: 'Zusendung von Werbung und Angeboten über elektronische Kommunikationswege (E-Mail, Messenger etc.)',
  },
  MARKETING_PHONE: {
    label: 'Telefonmarketing',
    description: 'Kontaktaufnahme zu Werbezwecken per Telefon',
  },
  DATA_SHARING_PARTNER: {
    label: 'Datenweitergabe',
    description: 'Weitergabe von Daten an Partnerunternehmen',
  },
};
