import prisma from '../lib/prisma.js';
import { stripHtml } from '../utils/sanitize.js';
import { validateHttpUrl } from '../utils/url.js';

// Default settings
const DEFAULT_SETTINGS: Record<string, string> = {
  customerSupportTicketsEnabled: 'false',
  // Vertrags-Cockpit: Fristenschwellen (in Tagen)
  deadlineCriticalDays: '14',      // Rot: Kritisch
  deadlineWarningDays: '42',       // Gelb: Warnung (6 Wochen)
  deadlineOkDays: '90',            // Grün: OK (3 Monate)
  // Ausweis-Ablauf: Fristenschwellen (in Tagen)
  documentExpiryCriticalDays: '30', // Rot: Kritisch (Standard 30 Tage)
  documentExpiryWarningDays: '90',  // Gelb: Warnung (Standard 90 Tage)
  // BLZ-/Bankdaten-Auto-Update (Bundesbank-Bankleitzahlen via bankdata-germany).
  // Der Updater lädt nur eine öffentliche Datendatei – keine Kundendaten.
  blzAutoUpdateEnabled: 'true',    // Auto-Update an/aus
  blzUpdateIntervalDays: '30',     // Prüf-/Update-Intervall in Tagen
};

// Whitelist erlaubter Setting-Keys. PUT /api/settings nimmt KEINE
// anderen Keys mehr an (Pentest Runde 11 (2026-05-18) – M1: Mass
// Assignment, "superAdminEmail", "debugMode", "allowedOrigins" landeten
// vorher ungefiltert in der DB).
export const ALLOWED_SETTING_KEYS: ReadonlySet<string> = new Set([
  ...Object.keys(DEFAULT_SETTINGS),
  'authorizationTemplateHtml',
  'imprintHtml',
  'privacyPolicyHtml',
  'websitePrivacyPolicyHtml',
  'monitoringAlertEmail',
  'monitoringDigestEnabled',
  'monitoringLastDigestAt',
  'companyName',
  'defaultEmailDomain',
  // Basis-URL für an Kunden verschickte Portal-Links (Login + Passwort-Reset).
  // Vorher kam aus `PUBLIC_URL`-Env, default localhost – Mails enthielten
  // dann unklickbare Links. Wird in Settings → Kundenportal gepflegt.
  'portalLoginUrl',
]);

export function isAllowedSettingKey(key: string): boolean {
  return ALLOWED_SETTING_KEYS.has(key);
}

// Keys deren Wert legitim HTML enthalten darf (Datenschutz-/Impressum-Editoren
// liefern WYSIWYG-HTML). Alle anderen Plain-Text-Keys (companyName,
// defaultEmailDomain, Schwellenwerte etc.) werden vor dem Persistieren durch
// stripHtml geschickt – Pentest 2026-05-19, MEDIUM: <img src=x onerror=alert(1)>
// in companyName landete ungefiltert in der DB und konnte später z.B. in
// E-Mail-Templates oder PDF-Generatoren unescaped landen.
const HTML_ALLOWED_SETTING_KEYS: ReadonlySet<string> = new Set([
  'authorizationTemplateHtml',
  'imprintHtml',
  'privacyPolicyHtml',
  'websitePrivacyPolicyHtml',
]);

/**
 * Bereinigt den Wert vor dem Speichern: für Plain-Text-Keys werden alle
 * HTML-Tags entfernt. Die dedizierten Editor-Keys
 * (imprintHtml/privacyPolicyHtml/...) bleiben unverändert, da sie sonst
 * den WYSIWYG-Editor unbenutzbar machen würden – sie werden über
 * dedizierte /api/gdpr-Endpoints gepflegt.
 */
export function sanitizeSettingValue(key: string, value: string): string {
  if (HTML_ALLOWED_SETTING_KEYS.has(key)) return value;
  const stripped = stripHtml(value);
  return typeof stripped === 'string' ? stripped : String(stripped);
}

/**
 * Schema-spezifische Wert-Validierung VOR dem Speichern. Wird vom
 * Controller aufgerufen; liefert entweder { ok: true, value: <sanitized> }
 * oder { ok: false, error: <message> } für 400.
 *
 * Hintergrund Pentest 2026-05-28 LOW 34.5: Schema-Whitelist und
 * Slash-Trimming standen NUR im Frontend, der API-Endpoint nahm
 * relative URLs (`/evil/path`), `javascript:`-Schemata und Adressen
 * auf private Hosts (`http://192.168.1.1`) ungeprüft entgegen. Bei
 * Cloud-Deployment war das ein SSRF-/Open-Redirect-Vektor in der
 * an Kunden verschickten Mail.
 */
export function validateSettingValue(key: string, rawValue: string): { ok: true; value: string } | { ok: false; error: string } {
  // Schwellenwerte: müssen positive ganze Zahlen sein, sonst läuft das
  // Cockpit in NaN-Vergleichen. Bestehende Validierung war nicht
  // konsequent.
  const intKeys = new Set(['deadlineCriticalDays', 'deadlineWarningDays', 'deadlineOkDays', 'documentExpiryCriticalDays', 'documentExpiryWarningDays']);
  if (intKeys.has(key)) {
    const trimmed = rawValue.trim();
    if (!/^\d+$/.test(trimmed)) {
      return { ok: false, error: `${key} muss eine positive ganze Zahl sein.` };
    }
    return { ok: true, value: trimmed };
  }

  // BLZ-Update-Intervall: mind. 1 Tag, höchstens 365 (verhindert Dauer-Polling
  // bzw. faktisch nie).
  if (key === 'blzUpdateIntervalDays') {
    const trimmed = rawValue.trim();
    if (!/^\d+$/.test(trimmed)) {
      return { ok: false, error: 'Das Intervall muss eine positive ganze Zahl (Tage) sein.' };
    }
    const n = parseInt(trimmed, 10);
    if (n < 1 || n > 365) {
      return { ok: false, error: 'Das Intervall muss zwischen 1 und 365 Tagen liegen.' };
    }
    return { ok: true, value: String(n) };
  }

  // Bool-Settings
  if (key === 'customerSupportTicketsEnabled' || key === 'monitoringDigestEnabled' || key === 'blzAutoUpdateEnabled') {
    const trimmed = rawValue.trim().toLowerCase();
    if (trimmed !== 'true' && trimmed !== 'false') {
      return { ok: false, error: `${key} muss 'true' oder 'false' sein.` };
    }
    return { ok: true, value: trimmed };
  }

  // Email-Settings (Format-Check analog zu Customer/User – verhindert
  // Header-Injection in System-Mails)
  if (key === 'monitoringAlertEmail') {
    const trimmed = rawValue.trim();
    if (trimmed === '') return { ok: true, value: '' };
    // RFC-5322-light, gleiches Pattern wie isValidEmail in utils/sanitize
    if (/[\r\n\t\0\v\f]/.test(trimmed) || trimmed.length > 254) {
      return { ok: false, error: 'Ungültige E-Mail-Adresse.' };
    }
    if (!/^[A-Za-z0-9._%+\-]{1,64}@[A-Za-z0-9.\-]{1,253}\.[A-Za-z]{2,}$/.test(trimmed)) {
      return { ok: false, error: 'Ungültiges E-Mail-Format.' };
    }
    return { ok: true, value: trimmed };
  }

  // Portal-Login-URL: nur http/https, keine privaten/loopback-Hosts.
  // Strikter als `isBlockedSsrfHost`, weil der Wert in Mails an Endkunden
  // landet – die können 127.0.0.1/10.x/172.16.x/192.168.x ohnehin nicht
  // erreichen. (Pentest Runde 35 LOW 34.5-followup, on-prem-Override
  // SSRF_BLOCK_PRIVATE_IPS gilt hier explizit NICHT.)
  // Werte mit Pfad/Query sind erlaubt – Mail-Versand hängt ohnehin
  // /portal/login hinten dran, eine evtl. Pfad-Komponente bleibt.
  if (key === 'portalLoginUrl') {
    return validateHttpUrl(rawValue, { fieldLabel: 'Portal-Login-URL' });
  }

  // Default: kein zusätzlicher Format-Check
  return { ok: true, value: rawValue };
}

export async function getSetting(key: string): Promise<string | null> {
  const setting = await prisma.appSetting.findUnique({
    where: { key },
  });

  if (setting) {
    return setting.value;
  }

  // Return default if exists
  return DEFAULT_SETTINGS[key] ?? null;
}

export async function getSettingBool(key: string): Promise<boolean> {
  const value = await getSetting(key);
  return value === 'true';
}

export async function setSetting(key: string, value: string): Promise<void> {
  await prisma.appSetting.upsert({
    where: { key },
    update: { value },
    create: { key, value },
  });
}

export async function getAllSettings(): Promise<Record<string, string>> {
  const settings = await prisma.appSetting.findMany();

  // Start with defaults, then override with stored values
  const result = { ...DEFAULT_SETTINGS };

  for (const setting of settings) {
    result[setting.key] = setting.value;
  }

  return result;
}

export async function getPublicSettings(): Promise<Record<string, string>> {
  // Settings that should be available to all authenticated users (including customers)
  const publicKeys = ['customerSupportTicketsEnabled'];
  const allSettings = await getAllSettings();

  const result: Record<string, string> = {};
  for (const key of publicKeys) {
    if (key in allSettings) {
      result[key] = allSettings[key];
    }
  }

  return result;
}
