import { Request, Response } from 'express';
import prisma from '../lib/prisma.js';
import * as customerService from '../services/customer.service.js';
import * as authService from '../services/auth.service.js';
import { logChange } from '../services/audit.service.js';
import { getConfiguredEmailDomains, emailUsesDomain } from '../services/emailProvider/emailProviderService.js';
import { validatePasswordComplexity, generateSecurePassword } from '../utils/passwordGenerator.js';
import { ApiResponse, AuthRequest } from '../types/index.js';
import {
  sanitizeCustomer,
  sanitizeCustomers,
  sanitizeCustomerStrict,
  pickCustomerCreate,
  pickCustomerUpdate,
  sanitizePhoneField,
  isValidEmail,
} from '../utils/sanitize.js';
import {
  canAccessMeter,
  canAccessAddress,
  canAccessBankCard,
  canAccessIdentityDocument,
  canAccessCustomer,
  getPortalAllowedCustomerIds,
} from '../utils/accessControl.js';
import { ApiError } from '../utils/apiError.js';

// Mass-Assignment-Schutz für Bankkarten (Pentest R155): nur diese Felder dürfen
// aus dem Request-Body übernommen werden – NICHT customerId/id/createdAt/… Nur
// tatsächlich gesendete Keys landen im Ergebnis, damit Partial-Updates und das
// Änderungs-Audit korrekt bleiben.
const BANK_CARD_ALLOWED_FIELDS = [
  'accountHolder', 'iban', 'cardNumber', 'bic', 'bankName', 'expiryDate', 'description', 'isActive',
] as const;

function pickBankCardFields(body: unknown): Record<string, unknown> {
  const src = (body && typeof body === 'object') ? (body as Record<string, unknown>) : {};
  const out: Record<string, unknown> = {};
  for (const key of BANK_CARD_ALLOWED_FIELDS) {
    if (key in src) out[key] = src[key];
  }
  return out;
}

// Customer CRUD
export async function getCustomers(req: AuthRequest, res: Response): Promise<void> {
  try {
    const { search, type, page, limit } = req.query;

    // Portal-User dürfen nur ihre eigenen + vertretene Kunden (mit aktiver
    // Vollmacht) sehen. Wir geben die Liste direkt als DB-Filter mit, damit
    // auch `pagination.total` nur über diese IDs zählt (Pentest Runde 6
    // MITTEL-02: `total: 4271` leakte vorher die globale Kunden-Zahl).
    const allowedIds = await getPortalAllowedCustomerIds(req);

    const result = await customerService.getAllCustomers({
      search: search as string,
      type: type as 'PRIVATE' | 'BUSINESS',
      page: page ? parseInt(page as string) : undefined,
      limit: limit ? parseInt(limit as string) : undefined,
      allowedIds: allowedIds ?? undefined,
    });
    const customers = result.customers as any[];

    // Portal-Kunden oder andere Rollen sehen kein portalPasswordEncrypted
    const canSeePasswords = req.user?.permissions?.includes('customers:update') ?? false;
    const sanitized = canSeePasswords
      ? sanitizeCustomers(customers)
      : customers.map((c) => sanitizeCustomerStrict(c)).filter(Boolean);
    res.json({ success: true, data: sanitized, pagination: result.pagination } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden der Kunden',
    } as ApiResponse);
  }
}

export async function getCustomer(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.id);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const customer = await customerService.getCustomerById(customerId);
    if (!customer) {
      res.status(404).json({ success: false, error: 'Kunde nicht gefunden' } as ApiResponse);
      return;
    }
    // Portal-Kunden/Read-only sehen kein portalPasswordEncrypted
    const canSeePasswords = req.user?.permissions?.includes('customers:update') ?? false;
    const sanitized = canSeePasswords
      ? sanitizeCustomer(customer as any)
      : sanitizeCustomerStrict(customer as any);
    res.json({ success: true, data: sanitized } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden des Kunden' } as ApiResponse);
  }
}

export async function createCustomer(req: Request, res: Response): Promise<void> {
  try {
    // Whitelist: nur erlaubte Felder aus req.body übernehmen
    const data: any = pickCustomerCreate(req.body);
    // E-Mail ist Pflichtfeld.
    if (!data.email || !String(data.email).trim()) {
      res.status(400).json({ success: false, error: 'E-Mail-Adresse ist ein Pflichtfeld.' } as ApiResponse);
      return;
    }
    // Email-Format prüfen, sonst landet "test@x.de\nBcc:evil@..." als
    // SMTP-Header-Injection-Vektor in der DB (Pentest 29.4).
    if (data.email && !isValidEmail(data.email)) {
      res.status(400).json({ success: false, error: 'Ungültiges E-Mail-Format' } as ApiResponse);
      return;
    }
    // Private E-Mail darf keine unserer verwalteten Provider-/Weiterleitungs-
    // Domains verwenden.
    if (emailUsesDomain(String(data.email), await getConfiguredEmailDomains())) {
      res.status(400).json({ success: false, error: 'Diese E-Mail liegt auf einer verwalteten (Weiterleitungs-/Mailbox-)Domain und ist als private Kunden-E-Mail nicht zulässig.' } as ApiResponse);
      return;
    }
    if (data.portalEmail && !isValidEmail(data.portalEmail)) {
      res.status(400).json({ success: false, error: 'Ungültiges Portal-E-Mail-Format' } as ApiResponse);
      return;
    }
    // 60.3: Phone/Mobile auch beim Create gegen Header-Injection sichern.
    try {
      if ('phone' in data) {
        const cleaned = sanitizePhoneField(data.phone, 'Telefon');
        data.phone = cleaned ?? null;
      }
      if ('mobile' in data) {
        const cleaned = sanitizePhoneField(data.mobile, 'Mobil');
        data.mobile = cleaned ?? null;
      }
    } catch (err) {
      res.status(400).json({ success: false, error: err instanceof Error ? err.message : 'Ungültige Telefonnummer' } as ApiResponse);
      return;
    }
    // Convert birthDate string to Date if present
    if (data.birthDate) {
      data.birthDate = new Date(data.birthDate);
    }
    const customer = await customerService.createCustomer(data);
    await logChange({
      req, action: 'CREATE', resourceType: 'Customer',
      resourceId: customer.id.toString(),
      label: `Kunde ${customer.customerNumber} angelegt (${customer.firstName} ${customer.lastName})`,
      customerId: customer.id,
    });
    // Response sanitisieren (Pentest Runde 15, 20.3/20.4): die Service-
    // Funktion gibt das rohe DB-Objekt mit portalPasswordHash + Reset-Token
    // zurück. Ohne sanitize-Aufruf leakte das beim Erstellen + Update.
    const canSeePasswords = (req as AuthRequest).user?.permissions?.includes('customers:update') ?? false;
    const sanitized = canSeePasswords
      ? sanitizeCustomer(customer as any)
      : sanitizeCustomerStrict(customer as any);
    res.status(201).json({ success: true, data: sanitized } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Kunden',
    } as ApiResponse);
  }
}

export async function updateCustomer(req: Request, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.id);
    // Whitelist: nur erlaubte Felder aus req.body übernehmen (Mass-Assignment-Schutz)
    // Email-Validierung gegen SMTP-Header-Injection (Pentest 29.4)
    if (req.body?.email && !isValidEmail(req.body.email)) {
      res.status(400).json({ success: false, error: 'Ungültiges E-Mail-Format' } as ApiResponse);
      return;
    }
    // Private E-Mail darf keine verwaltete Provider-/Weiterleitungs-Domain sein.
    if (req.body?.email && emailUsesDomain(String(req.body.email), await getConfiguredEmailDomains())) {
      res.status(400).json({ success: false, error: 'Diese E-Mail liegt auf einer verwalteten (Weiterleitungs-/Mailbox-)Domain und ist als private Kunden-E-Mail nicht zulässig.' } as ApiResponse);
      return;
    }
    if (req.body?.portalEmail && !isValidEmail(req.body.portalEmail)) {
      res.status(400).json({ success: false, error: 'Ungültiges Portal-E-Mail-Format' } as ApiResponse);
      return;
    }
    const data: any = pickCustomerUpdate(req.body);
    // E-Mail ist nur beim ANLEGEN Pflicht – beim Bearbeiten darf ein
    // Bestandskunde ohne E-Mail bleiben (die Domain-Prüfung oben greift aber
    // weiterhin, falls doch eine gesetzt wird).

    // Pentest 60.3 (MEDIUM, 2026-06-01): pickCustomerUpdate macht nur
    // stripHtml; CRLF und andere Control-Chars überlebten. Phone/Mobile
    // jetzt zusätzlich durch sanitizePhoneField (Allowlist).
    try {
      if ('phone' in data) {
        const cleaned = sanitizePhoneField(data.phone, 'Telefon');
        data.phone = cleaned ?? null;
      }
      if ('mobile' in data) {
        const cleaned = sanitizePhoneField(data.mobile, 'Mobil');
        data.mobile = cleaned ?? null;
      }
    } catch (err) {
      res.status(400).json({ success: false, error: err instanceof Error ? err.message : 'Ungültige Telefonnummer' } as ApiResponse);
      return;
    }

    // Vorherigen Stand laden für Audit
    const before = await prisma.customer.findUnique({ where: { id: customerId } });

    // Convert birthDate string to Date if present, empty string to null
    if (data.birthDate === '' || data.birthDate === null) {
      data.birthDate = null;
    } else if (data.birthDate) {
      data.birthDate = new Date(data.birthDate);
    }
    // Leere Strings in optionalen Feldern zu null konvertieren
    const nullableFields = ['salutation', 'birthPlace', 'phone', 'mobile', 'email', 'companyName', 'taxNumber', 'businessRegistration', 'commercialRegister', 'commercialRegisterNumber', 'notes'];
    for (const field of nullableFields) {
      if (data[field] === '') data[field] = null;
    }
    const customer = await customerService.updateCustomer(customerId, data);

    // Audit: Geänderte Felder ermitteln und loggen
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        salutation: 'Anrede', firstName: 'Vorname', lastName: 'Nachname', email: 'E-Mail',
        phone: 'Telefon', mobile: 'Mobil', birthDate: 'Geburtsdatum', birthPlace: 'Geburtsort',
        companyName: 'Firma', type: 'Typ', taxNumber: 'Steuernummer', notes: 'Notizen',
        useInformalAddress: 'Anrede per',
        autoBirthdayGreeting: 'Autom. Geburtstagsgruß',
        autoBirthdayChannel: 'Kanal für Geburtstagsgruß',
      };
      for (const [key, value] of Object.entries(data)) {
        // Technische/interne Felder überspringen
        if (['id', 'createdAt', 'updatedAt', 'customerNumber', 'portalPasswordHash', 'portalPasswordEncrypted'].includes(key)) continue;

        const oldVal = (before as any)[key];
        const newVal = value;
        // Normalisieren: null, undefined, "" werden alle als "leer" behandelt
        const normalize = (v: unknown) => {
          if (v === null || v === undefined || v === '') return null;
          if (v instanceof Date) return v.toISOString().split('T')[0];
          return v;
        };
        const oldNorm = normalize(oldVal);
        const newNorm = normalize(newVal);
        if (JSON.stringify(oldNorm) !== JSON.stringify(newNorm)) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (v instanceof Date) return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      if (Object.keys(changes).length > 0) {
        const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
        await logChange({
          req, action: 'UPDATE', resourceType: 'Customer',
          resourceId: customerId.toString(),
          label: `Kunde ${before.customerNumber} aktualisiert: ${changeList}`,
          details: changes,
          customerId,
        });
      }
    }

    // Response sanitisieren – sonst leakt portalPasswordHash +
    // portalPasswordResetToken + consentHash + portalPasswordMustChange.
    // Pentest Runde 15 (20.3 KRITISCH, 20.4 HOCH).
    const canSeePasswords = (req as AuthRequest).user?.permissions?.includes('customers:update') ?? false;
    const sanitized = canSeePasswords
      ? sanitizeCustomer(customer as any)
      : sanitizeCustomerStrict(customer as any);
    res.json({ success: true, data: sanitized } as ApiResponse);
  } catch (error) {
    console.error('Update customer error:', error);
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Kunden',
    } as ApiResponse);
  }
}

export async function deleteCustomer(req: Request, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.id);
    const customer = await prisma.customer.findUnique({ where: { id: customerId }, select: { customerNumber: true, firstName: true, lastName: true } });
    await customerService.deleteCustomer(customerId);
    await logChange({
      req, action: 'DELETE', resourceType: 'Customer',
      resourceId: customerId.toString(),
      label: `Kunde ${customer?.customerNumber} gelöscht (${customer?.firstName} ${customer?.lastName})`,
      customerId,
    });
    res.json({ success: true, message: 'Kunde gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen des Kunden',
    } as ApiResponse);
  }
}

// Addresses
export async function getAddresses(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const addresses = await customerService.getCustomerAddresses(customerId);
    res.json({ success: true, data: addresses } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Adressen' } as ApiResponse);
  }
}

export async function createAddress(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const address = await customerService.createAddress(customerId, req.body);
    await logChange({
      req, action: 'CREATE', resourceType: 'Address',
      resourceId: address.id.toString(),
      label: `Adresse hinzugefügt für Kunde #${customerId}`,
      customerId,
    });
    res.status(201).json({ success: true, data: address } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Adresse',
    } as ApiResponse);
  }
}

export async function updateAddress(req: AuthRequest, res: Response): Promise<void> {
  try {
    const addressId = parseInt(req.params.id);
    if (!(await canAccessAddress(req, res, addressId))) return;
    const data = req.body;

    // Vorherigen Stand laden für Audit
    const before = await prisma.address.findUnique({ where: { id: addressId } });

    const address = await customerService.updateAddress(addressId, data);
    const customerId = address.customerId;

    // Audit: Geänderte Felder ermitteln und loggen
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        street: 'Straße', houseNumber: 'Hausnummer', postalCode: 'PLZ',
        city: 'Stadt', country: 'Land', type: 'Typ', isDefault: 'Standard',
        ownerCompany: 'Eigentümer Firma', ownerFirstName: 'Eigentümer Vorname',
        ownerLastName: 'Eigentümer Nachname', ownerStreet: 'Eigentümer Straße',
        ownerHouseNumber: 'Eigentümer Hausnr.', ownerPostalCode: 'Eigentümer PLZ',
        ownerCity: 'Eigentümer Ort', ownerPhone: 'Eigentümer Telefon',
        ownerMobile: 'Eigentümer Mobil', ownerEmail: 'Eigentümer E-Mail',
      };
      for (const [key, newVal] of Object.entries(data)) {
        if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
        const oldVal = (before as any)[key];
        const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
        if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
      await logChange({
        req, action: 'UPDATE', resourceType: 'Address',
        resourceId: address.id.toString(),
        label: changeList ? `Adresse aktualisiert für Kunde #${customerId}: ${changeList}` : `Adresse aktualisiert für Kunde #${customerId}`,
        details: Object.keys(changes).length > 0 ? changes : undefined,
        customerId,
      });
    } else {
      await logChange({
        req, action: 'UPDATE', resourceType: 'Address',
        resourceId: address.id.toString(),
        label: `Adresse aktualisiert für Kunde #${customerId}`,
        customerId,
      });
    }

    res.json({ success: true, data: address } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Adresse',
    } as ApiResponse);
  }
}

export async function deleteAddress(req: AuthRequest, res: Response): Promise<void> {
  try {
    const addressId = parseInt(req.params.id);
    if (!(await canAccessAddress(req, res, addressId))) return;
    const addr = await prisma.address.findUnique({ where: { id: addressId }, select: { customerId: true } });
    const customerId = addr?.customerId;
    await customerService.deleteAddress(addressId);
    await logChange({
      req, action: 'DELETE', resourceType: 'Address',
      resourceId: addressId.toString(),
      label: `Adresse gelöscht für Kunde #${customerId}`,
      customerId: customerId ?? undefined,
    });
    res.json({ success: true, message: 'Adresse gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen der Adresse',
    } as ApiResponse);
  }
}

// Bank Cards
export async function getBankCards(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const showInactive = req.query.showInactive === 'true';
    const cards = await customerService.getCustomerBankCards(customerId, showInactive);
    res.json({ success: true, data: cards } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Bankkarten' } as ApiResponse);
  }
}

export async function createBankCard(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const data = pickBankCardFields(req.body);
    const card = await customerService.createBankCard(customerId, data as any);
    await logChange({
      req, action: 'CREATE', resourceType: 'BankCard',
      resourceId: card.id.toString(),
      label: `Bankverbindung hinzugefügt für Kunde #${customerId}`,
      customerId,
    });
    res.status(201).json({ success: true, data: card } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen der Bankkarte',
    } as ApiResponse);
  }
}

export async function updateBankCard(req: AuthRequest, res: Response): Promise<void> {
  try {
    const cardId = parseInt(req.params.id);
    if (!(await canAccessBankCard(req, res, cardId))) return;
    // Mass-Assignment-Schutz (Pentest R155): nur erlaubte Felder aus dem Body,
    // kein customerId/id/Fremdfeld. Das Audit unten läuft über diese saubere
    // Menge (keine Phantom-Einträge). Enforcement zusätzlich im Service.
    const data = pickBankCardFields(req.body);

    // Vorherigen Stand laden für Audit
    const before = await prisma.bankCard.findUnique({ where: { id: cardId } });

    const card = await customerService.updateBankCard(cardId, data);
    const customerId = card.customerId;

    // Audit: Geänderte Felder ermitteln und loggen
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        iban: 'IBAN', bic: 'BIC', bankName: 'Bank', cardNumber: 'Kartennummer',
        accountHolder: 'Kontoinhaber', isActive: 'Aktiv',
      };
      for (const [key, newVal] of Object.entries(data)) {
        if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
        const oldVal = (before as any)[key];
        const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
        if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
      await logChange({
        req, action: 'UPDATE', resourceType: 'BankCard',
        resourceId: card.id.toString(),
        label: changeList ? `Bankverbindung aktualisiert für Kunde #${customerId}: ${changeList}` : `Bankverbindung aktualisiert für Kunde #${customerId}`,
        details: Object.keys(changes).length > 0 ? changes : undefined,
        customerId,
      });
    } else {
      await logChange({
        req, action: 'UPDATE', resourceType: 'BankCard',
        resourceId: card.id.toString(),
        label: `Bankverbindung aktualisiert für Kunde #${customerId}`,
        customerId,
      });
    }

    res.json({ success: true, data: card } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Bankkarte',
    } as ApiResponse);
  }
}

export async function deleteBankCard(req: AuthRequest, res: Response): Promise<void> {
  try {
    const cardId = parseInt(req.params.id);
    if (!(await canAccessBankCard(req, res, cardId))) return;
    const card = await prisma.bankCard.findUnique({ where: { id: cardId }, select: { customerId: true } });
    const customerId = card?.customerId;
    await customerService.deleteBankCard(cardId);
    await logChange({
      req, action: 'DELETE', resourceType: 'BankCard',
      resourceId: cardId.toString(),
      label: `Bankverbindung gelöscht für Kunde #${customerId}`,
      customerId: customerId ?? undefined,
    });
    res.json({ success: true, message: 'Bankkarte gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen der Bankkarte',
    } as ApiResponse);
  }
}

// Identity Documents
export async function getDocuments(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const showInactive = req.query.showInactive === 'true';
    const docs = await customerService.getCustomerDocuments(customerId, showInactive);
    res.json({ success: true, data: docs } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Ausweise' } as ApiResponse);
  }
}

export async function createDocument(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const doc = await customerService.createDocument(customerId, req.body);
    await logChange({
      req, action: 'CREATE', resourceType: 'IdentityDocument',
      resourceId: doc.id.toString(),
      label: `Ausweis hinzugefügt für Kunde #${customerId}`,
      customerId,
    });
    res.status(201).json({ success: true, data: doc } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Ausweises',
    } as ApiResponse);
  }
}

export async function updateDocument(req: AuthRequest, res: Response): Promise<void> {
  try {
    const docId = parseInt(req.params.id);
    if (!(await canAccessIdentityDocument(req, res, docId))) return;
    const data = req.body;

    // Vorherigen Stand laden für Audit
    const before = await prisma.identityDocument.findUnique({ where: { id: docId } });

    const doc = await customerService.updateDocument(docId, data);
    const customerId = doc.customerId;

    // Audit: Geänderte Felder ermitteln und loggen
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        type: 'Dokumenttyp', documentNumber: 'Dokumentnummer',
        issuingAuthority: 'Ausstellungsbehörde', issueDate: 'Ausstellungsdatum',
        expiryDate: 'Ablaufdatum', isActive: 'Aktiv', licenseClasses: 'Führerscheinklassen',
      };
      for (const [key, newVal] of Object.entries(data)) {
        if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
        const oldVal = (before as any)[key];
        const norm = (v: unknown) => {
          if (v === null || v === undefined || v === '') return null;
          if (v instanceof Date) return v.toISOString().split('T')[0];
          return v;
        };
        if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (v instanceof Date) return v.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
      await logChange({
        req, action: 'UPDATE', resourceType: 'IdentityDocument',
        resourceId: doc.id.toString(),
        label: changeList ? `Ausweis aktualisiert für Kunde #${customerId}: ${changeList}` : `Ausweis aktualisiert für Kunde #${customerId}`,
        details: Object.keys(changes).length > 0 ? changes : undefined,
        customerId,
      });
    } else {
      await logChange({
        req, action: 'UPDATE', resourceType: 'IdentityDocument',
        resourceId: doc.id.toString(),
        label: `Ausweis aktualisiert für Kunde #${customerId}`,
        customerId,
      });
    }

    res.json({ success: true, data: doc } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Ausweises',
    } as ApiResponse);
  }
}

export async function deleteDocument(req: AuthRequest, res: Response): Promise<void> {
  try {
    const docId = parseInt(req.params.id);
    if (!(await canAccessIdentityDocument(req, res, docId))) return;
    const doc = await prisma.identityDocument.findUnique({ where: { id: docId }, select: { customerId: true } });
    const customerId = doc?.customerId;
    await customerService.deleteDocument(docId);
    await logChange({
      req, action: 'DELETE', resourceType: 'IdentityDocument',
      resourceId: docId.toString(),
      label: `Ausweis gelöscht für Kunde #${customerId}`,
      customerId: customerId ?? undefined,
    });
    res.json({ success: true, message: 'Ausweis gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen des Ausweises',
    } as ApiResponse);
  }
}

// Meters
export async function getMeters(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const showInactive = req.query.showInactive === 'true';
    const meters = await customerService.getCustomerMeters(customerId, showInactive);
    res.json({ success: true, data: meters } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Zähler' } as ApiResponse);
  }
}

export async function createMeter(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const meter = await customerService.createMeter(customerId, req.body);
    const successorLabel = meter.predecessor
      ? ` als Folgezähler von ${meter.predecessor.meterNumber}`
      : '';
    await logChange({
      req, action: 'CREATE', resourceType: 'Meter',
      resourceId: meter.id.toString(),
      label: `Zähler angelegt${successorLabel} für Kunde #${customerId}`,
      customerId,
    });
    res.status(201).json({ success: true, data: meter } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Erstellen des Zählers',
    } as ApiResponse);
  }
}

export async function updateMeter(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.id);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const data = req.body;

    // Vorherigen Stand laden für Audit
    const before = await prisma.meter.findUnique({ where: { id: meterId } });

    const meter = await customerService.updateMeter(meterId, data);
    const customerId = meter.customerId;

    // Audit: Geänderte Felder ermitteln und loggen
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        meterNumber: 'Zählernummer', type: 'Typ', tariffModel: 'Tarifmodell',
        location: 'Standort', isActive: 'Aktiv', addressId: 'Lieferadresse',
      };
      for (const [key, newVal] of Object.entries(data)) {
        if (['id', 'createdAt', 'updatedAt'].includes(key)) continue;
        const oldVal = (before as any)[key];
        const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
        if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
      await logChange({
        req, action: 'UPDATE', resourceType: 'Meter',
        resourceId: meter.id.toString(),
        label: changeList ? `Zähler aktualisiert: ${changeList}` : `Zähler aktualisiert`,
        details: Object.keys(changes).length > 0 ? changes : undefined,
        customerId,
      });
    } else {
      await logChange({
        req, action: 'UPDATE', resourceType: 'Meter',
        resourceId: meter.id.toString(),
        label: `Zähler aktualisiert`,
      });
    }

    res.json({ success: true, data: meter } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählers',
    } as ApiResponse);
  }
}

export async function deleteMeter(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.id);
    if (!(await canAccessMeter(req, res, meterId))) return;
    await customerService.deleteMeter(meterId);
    await logChange({
      req, action: 'DELETE', resourceType: 'Meter',
      resourceId: meterId.toString(),
      label: `Zähler gelöscht`,
    });
    res.json({ success: true, message: 'Zähler gelöscht' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählers',
    } as ApiResponse);
  }
}

// Meter Readings
export async function getMeterReadings(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.meterId);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const readings = await customerService.getMeterReadings(meterId);
    res.json({ success: true, data: readings } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Zählerstände' } as ApiResponse);
  }
}

export async function addMeterReading(req: AuthRequest, res: Response): Promise<void> {
  try {
    const { readingDate, value, valueNt, unit, notes } = req.body;
    const meterId = parseInt(req.params.meterId);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const reading = await customerService.addMeterReading(meterId, {
      readingDate: new Date(readingDate),
      value: parseFloat(value),
      valueNt: valueNt !== undefined && valueNt !== null && valueNt !== '' ? parseFloat(valueNt) : undefined,
      unit,
      notes,
    });

    // Audit: Zählerstand mit Kontext loggen
    const meter = await prisma.meter.findUnique({
      where: { id: meterId },
      select: { meterNumber: true, customer: { select: { id: true, firstName: true, lastName: true } } },
    });
    if (meter) {
      const ntInfo = valueNt ? ` / NT: ${parseFloat(valueNt)}` : '';
      await logChange({
        req, action: 'CREATE', resourceType: 'MeterReading',
        label: `Zählerstand ${parseFloat(value)}${ntInfo} ${unit || 'kWh'} für Zähler ${meter.meterNumber} erfasst (${meter.customer.firstName} ${meter.customer.lastName})`,
        details: { zähler: meter.meterNumber, stand: parseFloat(value), datum: readingDate },
        customerId: meter.customer.id,
      });
    }

    res.status(201).json({ success: true, data: reading } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Zählerstands',
    } as ApiResponse);
  }
}

export async function updateMeterReading(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.meterId);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const { readingDate, value, valueNt, unit, notes } = req.body;
    const updateData: Record<string, unknown> = {};
    if (readingDate !== undefined) updateData.readingDate = new Date(readingDate);
    if (value !== undefined) updateData.value = parseFloat(value);
    if (valueNt !== undefined) updateData.valueNt = valueNt !== null && valueNt !== '' ? parseFloat(valueNt) : null;
    if (unit !== undefined) updateData.unit = unit;
    if (notes !== undefined) updateData.notes = notes;

    const reading = await customerService.updateMeterReading(
      meterId,
      parseInt(req.params.readingId),
      updateData as any
    );
    await logChange({
      req, action: 'UPDATE', resourceType: 'MeterReading',
      resourceId: reading.id.toString(),
      label: `Zählerstand aktualisiert`,
    });
    res.json({ success: true, data: reading } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren des Zählerstands',
    } as ApiResponse);
  }
}

export async function deleteMeterReading(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.meterId);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const readingId = parseInt(req.params.readingId);
    await customerService.deleteMeterReading(meterId, readingId);
    await logChange({
      req, action: 'DELETE', resourceType: 'MeterReading',
      resourceId: readingId.toString(),
      label: `Zählerstand gelöscht`,
    });
    res.json({ success: true, data: null } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Löschen des Zählerstands',
    } as ApiResponse);
  }
}

// ==================== PORTAL: ZÄHLERSTAND MELDEN ====================

export async function reportMeterReading(req: AuthRequest, res: Response): Promise<void> {
  try {
    const user = req.user as any;
    if (!user?.isCustomerPortal || !user?.customerId) {
      res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' } as ApiResponse);
      return;
    }

    const meterId = parseInt(req.params.meterId);
    const { value, readingDate, notes } = req.body;

    // Prüfe ob der Zähler zum Kunden gehört
    const meter = await prisma.meter.findUnique({
      where: { id: meterId },
      select: { customerId: true },
    });

    if (!meter || meter.customerId !== user.customerId) {
      res.status(403).json({ success: false, error: 'Kein Zugriff auf diesen Zähler' } as ApiResponse);
      return;
    }

    const parsedDate = readingDate ? new Date(readingDate) : new Date();
    const parsedValue = parseFloat(value);

    // Validierung über den Service (monoton steigend)
    const reading = await customerService.addMeterReading(meterId, {
      readingDate: parsedDate,
      value: parsedValue,
      notes,
    });

    // Status auf REPORTED setzen
    await prisma.meterReading.update({
      where: { id: reading.id },
      data: { reportedBy: user.email, status: 'REPORTED' },
    });

    // Audit
    const meterInfo = await prisma.meter.findUnique({ where: { id: meterId }, select: { meterNumber: true } });
    await logChange({
      req, action: 'CREATE', resourceType: 'MeterReading',
      label: `Zählerstand ${parsedValue} gemeldet (Zähler ${meterInfo?.meterNumber || meterId})`,
      details: { zähler: meterInfo?.meterNumber, stand: parsedValue, datum: parsedDate.toISOString() },
      customerId: user.customerId,
    });

    res.status(201).json({ success: true, data: reading } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Melden des Zählerstands',
    } as ApiResponse);
  }
}

export async function getMyMeters(req: AuthRequest, res: Response): Promise<void> {
  try {
    const user = req.user as any;
    if (!user?.isCustomerPortal || !user?.customerId) {
      res.status(403).json({ success: false, error: 'Nur für Kundenportal-Benutzer' } as ApiResponse);
      return;
    }

    const meters = await prisma.meter.findMany({
      where: { customerId: user.customerId, isActive: true },
      include: {
        readings: {
          orderBy: { readingDate: 'desc' },
          take: 5,
        },
      },
      orderBy: { createdAt: 'asc' },
    });

    res.json({ success: true, data: meters } as ApiResponse);
  } catch (error) {
    res.status(500).json({ success: false, error: 'Fehler beim Laden der Zähler' } as ApiResponse);
  }
}

export async function markReadingTransferred(req: AuthRequest, res: Response): Promise<void> {
  try {
    const meterId = parseInt(req.params.meterId);
    if (!(await canAccessMeter(req, res, meterId))) return;
    const readingId = parseInt(req.params.readingId);

    const reading = await prisma.meterReading.update({
      where: { id: readingId },
      data: {
        status: 'TRANSFERRED',
        transferredAt: new Date(),
        transferredBy: req.user?.email,
      },
    });

    await logChange({
      req, action: 'UPDATE', resourceType: 'MeterReading',
      resourceId: readingId.toString(),
      label: `Zählerstand als übertragen markiert`,
    });

    res.json({ success: true, data: reading } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren',
    } as ApiResponse);
  }
}

// ==================== PORTAL SETTINGS ====================

export async function getPortalSettings(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const settings = await customerService.getPortalSettings(customerId);
    if (!settings) {
      res.status(404).json({ success: false, error: 'Kunde nicht gefunden' } as ApiResponse);
      return;
    }
    // Passwort-Hash nicht zurückgeben, nur ob ein Passwort gesetzt ist
    res.json({
      success: true,
      data: {
        id: settings.id,
        portalEnabled: settings.portalEnabled,
        portalEmail: settings.portalEmail,
        portalLastLogin: settings.portalLastLogin,
        hasPassword: !!settings.portalPasswordHash,
      },
    } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden der Portal-Einstellungen',
    } as ApiResponse);
  }
}

export async function updatePortalSettings(req: Request, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    // `password` (oder password-ähnliche Felder) gehören NICHT in den
    // Settings-Update. Sonst denkt der Client, sein Passwort wurde gesetzt
    // (HTTP 200), während das Feld stillschweigend ignoriert wird. Wer
    // ein Passwort setzen will, nutzt POST /portal/password mit
    // Komplexitäts-Check. (Pentest-Befund.)
    const body = req.body || {};
    const forbidden = ['password', 'portalPassword', 'portalPasswordHash', 'portalPasswordEncrypted'];
    const offending = forbidden.filter((k) => k in body);
    if (offending.length > 0) {
      res.status(400).json({
        success: false,
        error: `Felder nicht erlaubt: ${offending.join(', ')}. Bitte POST /customers/${customerId}/portal/password nutzen.`,
      } as ApiResponse);
      return;
    }
    const { portalEnabled, portalEmail } = body;
    // Email-Validierung gegen SMTP-Header-Injection (Pentest 29.4)
    if (portalEmail && !isValidEmail(portalEmail)) {
      res.status(400).json({ success: false, error: 'Ungültiges Portal-E-Mail-Format' } as ApiResponse);
      return;
    }

    // Vorherigen Stand laden für Audit
    const before = await prisma.customer.findUnique({
      where: { id: customerId },
      select: { portalEnabled: true, portalEmail: true },
    });

    const settings = await customerService.updatePortalSettings(customerId, {
      portalEnabled,
      portalEmail,
    });

    // Audit: Geänderte Felder ermitteln und loggen
    const data: Record<string, unknown> = { portalEnabled, portalEmail };
    if (before) {
      const changes: Record<string, { von: unknown; nach: unknown }> = {};
      const fieldLabels: Record<string, string> = {
        portalEnabled: 'Portal aktiv', portalEmail: 'Portal-E-Mail',
      };
      for (const [key, newVal] of Object.entries(data)) {
        if (newVal === undefined) continue;
        const oldVal = (before as any)[key];
        const norm = (v: unknown) => (v === null || v === undefined || v === '' ? null : v);
        if (JSON.stringify(norm(oldVal)) !== JSON.stringify(norm(newVal))) {
          const label = fieldLabels[key] || key;
          const formatVal = (v: unknown) => {
            if (v === null || v === undefined || v === '') return '-';
            if (typeof v === 'boolean') return v ? 'Ja' : 'Nein';
            return String(v);
          };
          changes[label] = { von: formatVal(oldVal), nach: formatVal(newVal) };
        }
      }
      const changeList = Object.entries(changes).map(([f, c]) => `${f}: ${c.von} → ${c.nach}`).join(', ');
      await logChange({
        req, action: 'UPDATE', resourceType: 'PortalSettings',
        resourceId: customerId.toString(),
        label: changeList ? `Portal-Einstellungen aktualisiert für Kunde #${customerId}: ${changeList}` : `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
        details: Object.keys(changes).length > 0 ? changes : undefined,
        customerId,
      });
    } else {
      await logChange({
        req, action: 'UPDATE', resourceType: 'PortalSettings',
        resourceId: customerId.toString(),
        label: `Portal-Einstellungen aktualisiert für Kunde #${customerId}`,
        customerId,
      });
    }

    res.json({ success: true, data: settings } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Aktualisieren der Portal-Einstellungen',
    } as ApiResponse);
  }
}

/**
 * Generiert ein zufälliges, komplexes Passwort (16 Zeichen, gemischt).
 * Setzt es NICHT direkt — wird im Frontend in den Setzen-Button-Flow gefüttert.
 * Damit hat der Admin Wahlfreiheit (Generieren → ggf. anpassen → speichern).
 */
export async function generatePortalPassword(req: Request, res: Response): Promise<void> {
  try {
    const password = generateSecurePassword({ length: 16 });
    res.json({ success: true, data: { password } } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Generieren des Passworts',
    } as ApiResponse);
  }
}

/**
 * Verschickt die Portal-Zugangsdaten per E-Mail an die hinterlegte
 * `email` (bevorzugt) oder fallback auf `portalEmail` des Kunden. Das
 * Passwort wird aus dem `portalPasswordEncrypted`-Feld entschlüsselt
 * (= das aktuell aktive Klartext-Passwort, das auch in der UI angezeigt wird).
 */
export async function sendPortalCredentials(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    const customer = await prisma.customer.findUnique({
      where: { id: customerId },
      select: {
        id: true, firstName: true, lastName: true, salutation: true, companyName: true,
        email: true, portalEmail: true, portalEnabled: true,
        portalPasswordEncrypted: true, portalPasswordHash: true,
      },
    });
    if (!customer) {
      res.status(404).json({ success: false, error: 'Kunde nicht gefunden' } as ApiResponse);
      return;
    }
    if (!customer.portalEnabled) {
      res.status(400).json({
        success: false,
        error: 'Portal ist für diesen Kunden nicht aktiviert',
      } as ApiResponse);
      return;
    }
    if (!customer.portalPasswordHash) {
      res.status(400).json({
        success: false,
        error: 'Es ist noch kein Portal-Passwort gesetzt',
      } as ApiResponse);
      return;
    }
    const targetEmail = customer.email || customer.portalEmail;
    if (!targetEmail) {
      res.status(400).json({
        success: false,
        error: 'Kunde hat keine E-Mail-Adresse hinterlegt',
      } as ApiResponse);
      return;
    }

    const loginEmail = customer.portalEmail || customer.email!;
    const pwResult = await authService.getCustomerPortalPassword(customerId);
    if (pwResult.status === 'none') {
      res.status(400).json({
        success: false,
        error: 'Klartext-Passwort nicht verfügbar (alte Anlage ohne Encrypted-Feld – bitte neu setzen)',
      } as ApiResponse);
      return;
    }
    // Desync: kein falsches Passwort versenden.
    if (pwResult.status === 'desync') {
      res.status(409).json({
        success: false,
        error: 'Das gespeicherte Passwort passt nicht zum Login-Passwort (Dateninkonsistenz). Bitte erst ein neues Passwort setzen/generieren, dann erneut senden.',
      } as ApiResponse);
      return;
    }

    await authService.sendPortalCredentialsEmail({
      to: targetEmail,
      customer,
      loginEmail,
      password: pwResult.password,
    });

    // Versendetes Passwort ist ein Einmalpasswort → beim ersten Login muss
    // der Kunde sich ein eigenes setzen.
    await authService.markPortalPasswordForChange(customerId);

    await logChange({
      req,
      action: 'UPDATE',
      resourceType: 'PortalSettings',
      resourceId: customerId.toString(),
      label: `Portal-Zugangsdaten per E-Mail versendet an ${targetEmail} (Einmalpasswort)`,
      customerId,
    });

    res.json({ success: true, message: `Zugangsdaten an ${targetEmail} versendet (Einmalpasswort)` } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Versenden der Zugangsdaten',
    } as ApiResponse);
  }
}

export async function setPortalPassword(req: Request, res: Response): Promise<void> {
  try {
    const { password } = req.body;
    // Komplexität: 12 Zeichen, Groß/Klein/Zahl/Sonderzeichen (zentrale Regel)
    const complexity = validatePasswordComplexity(password);
    if (!complexity.ok) {
      res.status(400).json({
        success: false,
        error: 'Passwort erfüllt Mindestanforderungen nicht: ' + complexity.errors.join(', '),
      } as ApiResponse);
      return;
    }
    const customerId = parseInt(req.params.customerId);
    await authService.setCustomerPortalPassword(customerId, password);
    await logChange({
      req, action: 'UPDATE', resourceType: 'PortalSettings',
      resourceId: customerId.toString(),
      label: `Portal-Passwort gesetzt für Kunde #${customerId}`,
      customerId,
    });
    res.json({ success: true, message: 'Passwort gesetzt' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Setzen des Passworts',
    } as ApiResponse);
  }
}

export async function getPortalPassword(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const result = await authService.getCustomerPortalPassword(customerId);
    // Klartext-Passwort-Read auditieren (CRITICAL): wer hat wann das Portal-
    // Passwort eines Kunden entschlüsselt? Wichtig für DSGVO-Nachvollziehbarkeit
    // + Insider-Threat-Erkennung.
    await logChange({
      req,
      action: 'READ',
      resourceType: 'PortalPassword',
      resourceId: customerId.toString(),
      label: `Klartext-Portal-Passwort von Kunde #${customerId} entschlüsselt (${result.status})`,
      customerId,
    });
    // Desync (gespeichertes Passwort passt nicht zum Login-Hash): NICHT ausgeben
    // – sonst liest der Support ein Passwort vor, das beim Login scheitert.
    if (result.status === 'desync') {
      res.status(409).json({
        success: false,
        error: 'Das gespeicherte Portal-Passwort stimmt nicht mit dem Login-Passwort überein (Dateninkonsistenz). Bitte über „Passwort setzen/neu generieren" ein neues Passwort vergeben.',
      } as ApiResponse);
      return;
    }
    res.json({ success: true, data: { password: result.status === 'ok' ? result.password : null } } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Abrufen des Passworts',
    } as ApiResponse);
  }
}

// ==================== REPRESENTATIVE MANAGEMENT ====================

export async function getRepresentatives(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    // Wer kann diesen Kunden vertreten (representedBy)?
    const representedBy = await customerService.getRepresentedByList(customerId);
    res.json({ success: true, data: representedBy } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler beim Laden der Vertreter',
    } as ApiResponse);
  }
}

export async function addRepresentative(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const { representativeId, notes } = req.body;
    const representative = await customerService.addRepresentative(
      customerId,
      parseInt(representativeId),
      notes
    );
    await logChange({
      req, action: 'CREATE', resourceType: 'Representative',
      resourceId: representative.id.toString(),
      label: `Vertreter hinzugefügt für Kunde #${customerId}`,
      customerId,
    });
    res.status(201).json({ success: true, data: representative } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Hinzufügen des Vertreters',
    } as ApiResponse);
  }
}

export async function removeRepresentative(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    await customerService.removeRepresentative(
      customerId,
      parseInt(req.params.representativeId)
    );
    await logChange({
      req, action: 'DELETE', resourceType: 'Representative',
      label: `Vertreter entfernt für Kunde #${customerId}`,
      customerId,
    });
    res.json({ success: true, message: 'Vertreter entfernt' } as ApiResponse);
  } catch (error) {
    res.status(400).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Entfernen des Vertreters',
    } as ApiResponse);
  }
}

// ==================== SALUTATION PREFERENCE ====================
// Per-User "Du"/"Sie" pro Kunde – User-Wunsch 2026-07-04.

export async function getSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!Number.isFinite(customerId) || customerId < 1) {
      res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
      return;
    }
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const userId = req.user?.userId;
    if (!userId) {
      res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
      return;
    }
    const result = await customerService.getSalutationPreference(userId, customerId);
    res.json({ success: true, data: result } as ApiResponse);
  } catch (error) {
    // Pentest R104.1: ApiError-Statuscodes durchreichen (404 statt 500
    // bei nicht-existierendem Kunden).
    const status = error instanceof ApiError ? error.statusCode : 500;
    res.status(status).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Laden der Anrede-Präferenz',
    } as ApiResponse);
  }
}

export async function setSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!Number.isFinite(customerId) || customerId < 1) {
      res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
      return;
    }
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const userId = req.user?.userId;
    if (!userId) {
      res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
      return;
    }
    const preference = req.body?.preference;
    if (preference !== 'DU' && preference !== 'SIE') {
      res.status(400).json({
        success: false,
        error: 'preference muss "DU" oder "SIE" sein',
      } as ApiResponse);
      return;
    }
    await customerService.setSalutationPreference(userId, customerId, preference);
    const result = await customerService.getSalutationPreference(userId, customerId);
    res.json({ success: true, data: result } as ApiResponse);
  } catch (error) {
    const status = error instanceof ApiError ? error.statusCode : 500;
    res.status(status).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Speichern der Anrede-Präferenz',
    } as ApiResponse);
  }
}

export async function clearSalutationPreference(req: AuthRequest, res: Response): Promise<void> {
  try {
    const customerId = parseInt(req.params.customerId);
    if (!Number.isFinite(customerId) || customerId < 1) {
      res.status(400).json({ success: false, error: 'Ungültige Kunden-ID' } as ApiResponse);
      return;
    }
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const userId = req.user?.userId;
    if (!userId) {
      res.status(401).json({ success: false, error: 'Nicht authentifiziert' } as ApiResponse);
      return;
    }
    await customerService.clearSalutationPreference(userId, customerId);
    const result = await customerService.getSalutationPreference(userId, customerId);
    res.json({ success: true, data: result } as ApiResponse);
  } catch (error) {
    const status = error instanceof ApiError ? error.statusCode : 500;
    res.status(status).json({
      success: false,
      error: error instanceof Error ? error.message : 'Fehler beim Zurücksetzen der Anrede-Präferenz',
    } as ApiResponse);
  }
}

export async function searchForRepresentative(req: AuthRequest, res: Response): Promise<void> {
  try {
    // KRITISCH (Pentest Runde 6): ohne canAccessCustomer kann ein Portal-User
    // mit beliebigem :customerId-Pfad alle Kunden durchsuchen → komplette
    // Kunden-DB-Enumeration via Buchstaben-Brute-Force.
    const customerId = parseInt(req.params.customerId);
    if (!(await canAccessCustomer(req, res, customerId))) return;
    const { search } = req.query;
    if (!search || typeof search !== 'string' || search.length < 2) {
      res.json({ success: true, data: [] } as ApiResponse);
      return;
    }
    const customers = await customerService.searchCustomersForRepresentative(
      search,
      customerId,
    );
    res.json({ success: true, data: customers } as ApiResponse);
  } catch (error) {
    res.status(500).json({
      success: false,
      error: 'Fehler bei der Suche',
    } as ApiResponse);
  }
}
