import { ContractType, ContractStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { generateContractNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
import { encrypt, decrypt } from '../utils/encryption.js';
import { sanitizeCustomerStrict, sanitizePhoneField } from '../utils/sanitize.js';

export interface ContractFilters {
  customerId?: number;
  customerIds?: number[];  // Für Kundenportal: eigene ID + vertretene Kunden
  type?: ContractType;
  status?: ContractStatus;
  search?: string;
  page?: number;
  limit?: number;
  includeDeactivated?: boolean;  // wenn true: auch DEACTIVATED-Verträge listen
}

export async function getAllContracts(filters: ContractFilters) {
  const { customerId, customerIds, type, status, search, page = 1, limit = 20, includeDeactivated = false } = filters;
  const { skip, take } = paginate(page, limit);

  const where: Record<string, unknown> = {};

  // Entweder Liste von customerIds (Kundenportal, fail-closed) ODER einzelne
  // customerId (Staff-Filter). Fail-closed (Pentest R145): ist customerIds
  // gesetzt – auch als LEERES Array – wird strikt darauf gefiltert (`IN ()`
  // → 0 Treffer). Nur `undefined` (Staff) überspringt den Filter.
  if (customerIds) {
    where.customerId = { in: customerIds };
  } else if (customerId) {
    where.customerId = customerId;
  }
  if (type) where.type = type;

  // Status-Filter: Deaktivierte Verträge standardmäßig ausblenden – außer
  // ein expliziter Status ist gesetzt oder includeDeactivated=true (z.B. für
  // das Vorgängervertrag-Dropdown, das auch deaktivierte Verträge als
  // Vorgänger zulassen muss).
  if (status) {
    where.status = status;
  } else if (!includeDeactivated) {
    where.status = { not: ContractStatus.DEACTIVATED };
  }

  if (search) {
    where.OR = [
      // Basis-Vertragsfelder
      { contractNumber: { contains: search } },
      { providerName: { contains: search } },
      { tariffName: { contains: search } },
      { customerNumberAtProvider: { contains: search } },
      { provider: { name: { contains: search } } },
      { tariff: { name: { contains: search } } },
      // Kundenname
      { customer: { firstName: { contains: search } } },
      { customer: { lastName: { contains: search } } },
      { customer: { companyName: { contains: search } } },
      { customer: { customerNumber: { contains: search } } },
      // Internet-Vertragsdetails
      { internetDetails: { routerSerialNumber: { contains: search } } },
      { internetDetails: { homeId: { contains: search } } },
      { internetDetails: { activationCode: { contains: search } } },
      { internetDetails: { phoneNumbers: { some: { phoneNumber: { contains: search } } } } },
      // Mobilfunk-Vertragsdetails
      { mobileDetails: { phoneNumber: { contains: search } } },
      { mobileDetails: { simCardNumber: { contains: search } } },
      { mobileDetails: { deviceImei: { contains: search } } },
      { mobileDetails: { simCards: { some: { phoneNumber: { contains: search } } } } },
      { mobileDetails: { simCards: { some: { simCardNumber: { contains: search } } } } },
      { mobileDetails: { simCards: { some: { cardUser: { contains: search } } } } },
      // Energie-Vertragsdetails (Zählernummer)
      { energyDetails: { meter: { meterNumber: { contains: search } } } },
      // TV-Vertragsdetails
      { tvDetails: { smartcardNumber: { contains: search } } },
      // KFZ-Versicherung
      { carInsuranceDetails: { licensePlate: { contains: search } } },
      { carInsuranceDetails: { vin: { contains: search } } },
      { carInsuranceDetails: { policyNumber: { contains: search } } },
    ];
  }

  const [contracts, total] = await Promise.all([
    prisma.contract.findMany({
      where,
      skip,
      take,
      orderBy: [{ createdAt: 'desc' }],
      include: {
        customer: {
          select: {
            id: true,
            customerNumber: true,
            firstName: true,
            lastName: true,
            companyName: true,
          },
        },
        address: true,
        billingAddress: true,
        salesPlatform: true,
        cancellationPeriod: true,
        contractDuration: true,
        provider: true,
        tariff: true,
        contractCategory: true,
        mobileDetails: {
          select: {
            phoneNumber: true,
            mobileNetwork: true,
            simCards: { select: { phoneNumber: true, isMain: true, cardUser: true } },
          },
        },
        // 2026-07-04: für Vertragsauswahl-Labels brauchen wir überall
        // die Zählernummer (Strom/Gas) und die erste Festnetznummer
        // (Internet). Beides light-select, damit das Payload klein bleibt.
        energyDetails: {
          select: {
            meter: { select: { id: true, meterNumber: true } },
          },
        },
        internetDetails: {
          select: {
            phoneNumbers: {
              select: { phoneNumber: true, isMain: true },
              orderBy: { id: 'asc' },
            },
          },
        },
        carInsuranceDetails: { select: { licensePlate: true } },
      },
    }),
    prisma.contract.count({ where }),
  ]);

  return {
    contracts,
    pagination: buildPaginationResponse(page, limit, total),
  };
}

export async function getContractById(id: number, decryptPassword = false) {
  const contract = await prisma.contract.findUnique({
    where: { id },
    include: {
      customer: true,
      address: true,
      billingAddress: true,
      bankCard: true,
      identityDocument: true,
      salesPlatform: true,
      cancellationPeriod: true,
      contractDuration: true,
      provider: true,
      tariff: true,
      contractCategory: true,
      previousProvider: true,
      previousContract: {
        include: {
          energyDetails: { include: { meter: { include: { readings: true } }, contractMeters: { include: { meter: { include: { readings: true } } }, orderBy: { position: 'asc' as const } }, invoices: true } },
          internetDetails: { include: { phoneNumbers: true } },
          mobileDetails: { include: { simCards: true } },
          tvDetails: true,
          carInsuranceDetails: true,
        },
      },
      energyDetails: { include: { meter: { include: { readings: true } }, contractMeters: { include: { meter: { include: { readings: true } } }, orderBy: { position: 'asc' as const } }, invoices: true } },
      internetDetails: { include: { phoneNumbers: true } },
      mobileDetails: { include: { simCards: true } },
      tvDetails: true,
      carInsuranceDetails: true,
      stressfreiEmail: true,
      invoices: { orderBy: { invoiceDate: 'desc' as const } },
      documents: { orderBy: { createdAt: 'desc' as const } },
      followUpContract: {
        select: { id: true, contractNumber: true, status: true },
      },
    },
  });

  if (!contract) return null;

  // SECURITY: Embedded Customer-Objekt sanitizen, sonst leaken
  // portalPasswordHash + portalPasswordEncrypted + Reset-Token in jede
  // contract.customer-Response. Der direkte `/customers/:id`-Endpoint hat
  // den Schutz schon; hier wäre er ohne Sanitize bypassbar.
  if (contract.customer) {
    (contract as Record<string, unknown>).customer = sanitizeCustomerStrict(
      contract.customer as Record<string, unknown>,
    );
  }

  // Decrypt password if requested and exists (Contract-Anbieter-Passwort,
  // nicht zu verwechseln mit Customer-Portal-Passwort)
  if (decryptPassword && contract.portalPasswordEncrypted) {
    try {
      (contract as Record<string, unknown>).portalPasswordDecrypted = decrypt(
        contract.portalPasswordEncrypted
      );
    } catch {
      // Password decryption failed, leave as is
    }
  }

  // Virtuelles Bool-Flag, damit das Frontend "PW gesetzt?" weiß, ohne dass
  // der verschlüsselte Blob in die Response leakt (sanitizeContract strippt
  // portalPasswordEncrypted bewusst). Pentest Runde 15 – sensitive Feld
  // raus aus /contracts/:id; UI nutzt jetzt `hasPortalPassword`.
  (contract as Record<string, unknown>).hasPortalPassword =
    !!contract.portalPasswordEncrypted;

  return contract;
}

interface ContractCreateData {
  customerId: number;
  type: ContractType;
  contractCategoryId?: number;
  status?: ContractStatus;
  addressId?: number;
  billingAddressId?: number;
  bankCardId?: number;
  identityDocumentId?: number;
  salesPlatformId?: number;
  previousContractId?: number;
  providerId?: number;
  tariffId?: number;
  providerName?: string;
  tariffName?: string;
  customerNumberAtProvider?: string;
  orderNumberAtSalesPlatform?: string;
  customerNumberAtSalesPlatform?: string;
  contractNumberAtSalesPlatform?: string;
  priceFirst12Months?: string;
  priceFrom13Months?: string;
  priceAfter24Months?: string;
  startDate?: Date;
  endDate?: Date;
  cancellationPeriodId?: number;
  contractDurationId?: number;
  commission?: number;
  portalUsername?: string;
  portalPassword?: string;
  stressfreiEmailId?: number;
  portalCredentialsNotRequired?: boolean;
  notes?: string;
  // Kündigungsdaten
  cancellationConfirmationDate?: Date;
  cancellationConfirmationOptionsDate?: Date;
  wasSpecialCancellation?: boolean;
  // Type-specific details
  energyDetails?: {
    meterId?: number;
    annualConsumption?: number;
    basePrice?: number;
    unitPrice?: number;
    instantBonus?: number;
    newCustomerBonus?: number;
    noBonusDesired?: boolean;
    previousProviderName?: string;
    previousCustomerNumber?: string;
  };
  internetDetails?: {
    downloadSpeed?: number;
    uploadSpeed?: number;
    routerModel?: string;
    routerSerialNumber?: string;
    installationDate?: Date;
    // Internet-Zugangsdaten
    internetUsername?: string;
    internetPassword?: string;
    // Objekt & Lage
    propertyType?: string;
    propertyLocation?: string;
    connectionLocation?: string;
    // Glasfaser-spezifisch
    homeId?: string;
    // Vodafone DSL/Kabel spezifisch
    activationCode?: string;
    phoneNumbers?: {
      id?: number;
      phoneNumber: string;
      areaCode?: string;
      isMain?: boolean;
      sipUsername?: string;
      sipPassword?: string;
      sipServer?: string;
    }[];
  };
  mobileDetails?: {
    requiresMultisim?: boolean;
    mobileNetwork?: string | null;
    dataVolume?: number;
    includedMinutes?: number;
    includedSMS?: number;
    deviceModel?: string;
    deviceImei?: string;
    // Legacy-Felder
    phoneNumber?: string;
    simCardNumber?: string;
    // SIM-Karten Liste
    simCards?: {
      id?: number;
      phoneNumber?: string;
      simCardNumber?: string;
      pin?: string;
      puk?: string;
      isMultisim?: boolean;
      isMain?: boolean;
      isEsim?: boolean;
      cardUser?: string;
    }[];
  };
  tvDetails?: {
    receiverModel?: string;
    smartcardNumber?: string;
    package?: string;
  };
  carInsuranceDetails?: {
    licensePlate?: string;
    hsn?: string;
    tsn?: string;
    vin?: string;
    vehicleType?: string;
    firstRegistration?: Date;
    noClaimsClass?: string;
    insuranceType?: 'LIABILITY' | 'PARTIAL' | 'FULL';
    deductiblePartial?: number;
    deductibleFull?: number;
    policyNumber?: string;
    previousInsurer?: string;
  };
}

// Erlaubte Mobilfunknetze. Server-seitige Whitelist, damit über einen
// handgebauten Request kein beliebiger String in mobileNetwork landet
// (das Frontend-Dropdown beschränkt schon, der Server vertraut ihm aber
// nicht). Unbekannte/leere Werte werden zu null normalisiert.
const ALLOWED_MOBILE_NETWORKS = new Set(['TELEKOM', 'VODAFONE', 'TELEFONICA']);
function normalizeMobileNetwork(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const v = value.trim().toUpperCase();
  return ALLOWED_MOBILE_NETWORKS.has(v) ? v : null;
}

// Feld-Whitelist für die Contract-Top-Level-Scalars (Mass-Assignment-Schutz,
// Pentest R158-Hygiene, konsistent zur R156-Härtung von BankCard/Address/
// Document). Nur diese Felder gehen aus dem (bereits um Nested-Details +
// portalPassword bereinigten) `contractData` an Prisma – NICHT id/contractNumber/
// createdAt/updatedAt oder sonstige untergeschobene Keys. `contractNumber` wird
// bei create serverseitig gesetzt, nie aus dem Body.
// Autoritativ aus den DB-Spalten abgeleitet (nicht aus dem unvollständigen
// ContractCreateData-Typ!). BEWUSST NICHT enthalten:
//  - id / contractNumber / createdAt / updatedAt  → nie client-setzbar
//  - portalPasswordEncrypted                       → via portalPassword-Verschlüsselung
//  - cancellation*Path (Schreiben/Bestätigung/Optionen) → nur über die
//    dedizierten Upload-/Delete-Endpunkte, nicht per Form-Update
const CONTRACT_SCALAR_FIELDS = [
  'customerId', 'type', 'contractCategoryId', 'status', 'addressId', 'billingAddressId',
  'bankCardId', 'identityDocumentId', 'salesPlatformId', 'previousContractId', 'providerId',
  'tariffId', 'providerName', 'tariffName', 'customerNumberAtProvider', 'contractNumberAtProvider',
  'orderNumberAtSalesPlatform',
  'customerNumberAtSalesPlatform', 'contractNumberAtSalesPlatform', 'priceFirst12Months',
  'priceFrom13Months', 'priceAfter24Months', 'startDate', 'endDate', 'cancellationPeriodId',
  'contractDurationId', 'commission', 'portalUsername', 'stressfreiEmailId',
  'portalCredentialsNotRequired', 'notes', 'cancellationConfirmationDate',
  'cancellationConfirmationOptionsDate', 'wasSpecialCancellation',
  // In ContractCreateData nicht typisiert, aber echte Spalten, die das Formular
  // sendet (liefen bisher nur über den rohen Spread durch):
  'previousProviderId', 'previousContractNumber', 'previousCustomerNumber', 'nextReviewDate',
] as const;

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

// Feld-Whitelists für die Nested-Detail-Modelle (Mass-Assignment-Schutz, Pentest
// R162-01). Autoritativ aus den DB-Spalten, jeweils OHNE id/contractId (PK/FK
// nie client-setzbar) und ohne Nested-Relationen (phoneNumbers/simCards/
// contractMeters) bzw. verschlüsselte Felder (internetPasswordEncrypted), die
// separat behandelt werden. Verhindert Reparenting/Orphaning per injizierter
// id/contractId im energy/mobile/tv/carInsurance-Objekt.
function makePick(fields: readonly string[]) {
  return (data: unknown): Record<string, unknown> => {
    const src = (data && typeof data === 'object') ? (data as Record<string, unknown>) : {};
    const out: Record<string, unknown> = {};
    for (const key of fields) if (key in src) out[key] = src[key];
    return out;
  };
}

const pickEnergyScalars = makePick([
  'meterId', 'annualConsumption', 'basePrice', 'unitPrice', 'previousProviderName',
  'previousCustomerNumber', 'annualConsumptionKwh', 'maloId', 'unitPriceNt',
  'instantBonus', 'newCustomerBonus', 'noBonusDesired',
]);
const pickMobileScalars = makePick([
  'phoneNumber', 'simCardNumber', 'dataVolume', 'includedMinutes', 'includedSMS',
  'deviceModel', 'deviceImei', 'requiresMultisim', 'mobileNetwork',
]);
const pickTvScalars = makePick(['receiverModel', 'smartcardNumber', 'package']);
const pickCarInsuranceScalars = makePick([
  'licensePlate', 'hsn', 'tsn', 'vin', 'vehicleType', 'firstRegistration',
  'noClaimsClass', 'insuranceType', 'deductiblePartial', 'deductibleFull',
  'policyNumber', 'previousInsurer',
]);

export async function createContract(data: ContractCreateData) {
  const {
    energyDetails,
    internetDetails,
    mobileDetails,
    tvDetails,
    carInsuranceDetails,
    portalPassword,
    ...contractData
  } = data;

  // Encrypt password if provided
  const portalPasswordEncrypted = portalPassword
    ? encrypt(portalPassword)
    : undefined;

  const contract = await prisma.contract.create({
    data: {
      ...(pickContractScalars(contractData) as any),
      contractNumber: generateContractNumber(data.type),
      portalPasswordEncrypted,
      ...(energyDetails && ['ELECTRICITY', 'GAS'].includes(data.type)
        ? { energyDetails: { create: pickEnergyScalars(energyDetails) as any } }
        : {}),
      ...(internetDetails && ['DSL', 'CABLE', 'FIBER'].includes(data.type)
        ? {
            internetDetails: {
              create: {
                downloadSpeed: internetDetails.downloadSpeed,
                uploadSpeed: internetDetails.uploadSpeed,
                routerModel: internetDetails.routerModel,
                routerSerialNumber: internetDetails.routerSerialNumber,
                installationDate: internetDetails.installationDate,
                internetUsername: internetDetails.internetUsername,
                internetPasswordEncrypted: internetDetails.internetPassword
                  ? encrypt(internetDetails.internetPassword)
                  : undefined,
                propertyType: internetDetails.propertyType,
                propertyLocation: internetDetails.propertyLocation,
                connectionLocation: internetDetails.connectionLocation,
                homeId: internetDetails.homeId,
                activationCode: internetDetails.activationCode,
                phoneNumbers: internetDetails.phoneNumbers && internetDetails.phoneNumbers.length > 0
                  ? {
                      create: internetDetails.phoneNumbers.map((pn) => ({
                        phoneNumber: sanitizePhoneField(pn.phoneNumber, 'Rufnummer') ?? '',
                        areaCode: sanitizePhoneField(pn.areaCode, 'Vorwahl'),
                        isMain: pn.isMain ?? false,
                        sipUsername: pn.sipUsername,
                        sipPasswordEncrypted: pn.sipPassword
                          ? encrypt(pn.sipPassword)
                          : undefined,
                        sipServer: pn.sipServer,
                      })),
                    }
                  : undefined,
              },
            },
          }
        : {}),
      ...(mobileDetails && data.type === 'MOBILE'
        ? {
            mobileDetails: {
              create: {
                requiresMultisim: mobileDetails.requiresMultisim,
                mobileNetwork: normalizeMobileNetwork(mobileDetails.mobileNetwork),
                dataVolume: mobileDetails.dataVolume,
                includedMinutes: mobileDetails.includedMinutes,
                includedSMS: mobileDetails.includedSMS,
                deviceModel: mobileDetails.deviceModel,
                deviceImei: mobileDetails.deviceImei,
                phoneNumber: mobileDetails.phoneNumber,
                simCardNumber: mobileDetails.simCardNumber,
                simCards: mobileDetails.simCards
                  ? {
                      create: mobileDetails.simCards.map((sc) => ({
                        phoneNumber: sc.phoneNumber,
                        simCardNumber: sc.simCardNumber,
                        pin: sc.pin ? encrypt(sc.pin) : undefined,
                        puk: sc.puk ? encrypt(sc.puk) : undefined,
                        isMultisim: sc.isMultisim ?? false,
                        isMain: sc.isMain ?? false,
                        isEsim: sc.isEsim ?? false,
                        cardUser: sc.cardUser,
                      })),
                    }
                  : undefined,
              },
            },
          }
        : {}),
      ...(tvDetails && data.type === 'TV'
        ? { tvDetails: { create: pickTvScalars(tvDetails) as any } }
        : {}),
      ...(carInsuranceDetails && data.type === 'CAR_INSURANCE'
        ? { carInsuranceDetails: { create: pickCarInsuranceScalars(carInsuranceDetails) as any } }
        : {}),
    },
    include: {
      customer: true,
      address: true,
      billingAddress: true,
      salesPlatform: true,
      energyDetails: true,
      internetDetails: { include: { phoneNumbers: true } },
      mobileDetails: { include: { simCards: true } },
      tvDetails: true,
      carInsuranceDetails: true,
    },
  });

  // Embedded Customer-Objekt sanitizen (siehe getContractById – derselbe
  // Schutz; createContract gibt den frisch erstellten Vertrag inkl. Customer
  // zurück, und der darf keine Passwort-Hashes/-Encryptions leaken).
  if (contract.customer) {
    (contract as Record<string, unknown>).customer = sanitizeCustomerStrict(
      contract.customer as Record<string, unknown>,
    );
  }

  return contract;
}

export async function updateContract(
  id: number,
  data: Partial<ContractCreateData>
) {
  const {
    energyDetails,
    internetDetails,
    mobileDetails,
    tvDetails,
    carInsuranceDetails,
    portalPassword,
    ...contractData
  } = data;

  // Encrypt password if provided
  const portalPasswordEncrypted = portalPassword
    ? encrypt(portalPassword)
    : undefined;

  // Opt-out „Portaldaten nicht benötigt": Alte Anmeldedaten explizit
  // wegräumen, damit nach dem Umschalten kein verwaister verschlüsselter
  // Blob + Username in der DB stehen bleibt. Nur greifen, wenn das Flag
  // in diesem Update wirklich auf true gesetzt wird (nicht bei jedem PUT).
  const clearCredentials = contractData.portalCredentialsNotRequired === true;

  // Update main contract – nur Whitelist-Felder (kein roher `...contractData`
  // mehr → kein Mass-Assignment von id/customerId-PK/contractNumber/Timestamps).
  await prisma.contract.update({
    where: { id },
    data: {
      ...(pickContractScalars(contractData) as any),
      ...(clearCredentials
        ? {
            portalUsername: null,
            portalPasswordEncrypted: null,
            stressfreiEmailId: null,
          }
        : portalPasswordEncrypted
          ? { portalPasswordEncrypted }
          : {}),
    },
  });

  // Update type-specific details
  if (energyDetails) {
    const existingEcd = await prisma.energyContractDetails.findUnique({
      where: { contractId: id },
      select: { id: true, meterId: true },
    });

    await prisma.energyContractDetails.upsert({
      where: { contractId: id },
      update: pickEnergyScalars(energyDetails) as any,
      create: { contractId: id, ...(pickEnergyScalars(energyDetails) as any) },
    });

    // ContractMeter synchronisieren wenn sich der Zähler ändert
    if (energyDetails.meterId !== undefined && existingEcd) {
      const oldMeterId = existingEcd.meterId;
      const newMeterId = energyDetails.meterId;

      if (oldMeterId !== newMeterId) {
        // Alle alten ContractMeter-Einträge entfernen
        await prisma.contractMeter.deleteMany({
          where: { energyContractDetailsId: existingEcd.id },
        });

        // Neuen ContractMeter-Eintrag erstellen (wenn ein Zähler gesetzt)
        if (newMeterId) {
          const contract = await prisma.contract.findUnique({
            where: { id },
            select: { startDate: true },
          });
          await prisma.contractMeter.create({
            data: {
              energyContractDetailsId: existingEcd.id,
              meterId: newMeterId,
              position: 0,
              installedAt: contract?.startDate,
            },
          });
        }
      }
    }
  }

  if (internetDetails) {
    const { phoneNumbers, internetPassword, ...internetData } = internetDetails;
    const existing = await prisma.internetContractDetails.findUnique({
      where: { contractId: id },
      include: { phoneNumbers: true },
    });

    // Prepare internet data with encryption
    const preparedInternetData = {
      downloadSpeed: internetData.downloadSpeed,
      uploadSpeed: internetData.uploadSpeed,
      routerModel: internetData.routerModel,
      routerSerialNumber: internetData.routerSerialNumber,
      installationDate: internetData.installationDate,
      internetUsername: internetData.internetUsername,
      // Only update password if new value provided, otherwise keep existing
      ...(internetPassword
        ? { internetPasswordEncrypted: encrypt(internetPassword) }
        : {}),
      propertyType: internetData.propertyType,
      propertyLocation: internetData.propertyLocation,
      connectionLocation: internetData.connectionLocation,
      homeId: internetData.homeId,
      activationCode: internetData.activationCode,
    };

    if (existing) {
      await prisma.internetContractDetails.update({
        where: { contractId: id },
        data: preparedInternetData,
      });

      if (phoneNumbers) {
        // Get existing phone numbers for preserving encrypted passwords
        const existingPhoneNumbers = existing.phoneNumbers || [];

        // Delete all existing phone numbers
        await prisma.phoneNumber.deleteMany({
          where: { internetContractDetailsId: existing.id },
        });

        // Create new phone numbers with encryption
        await prisma.phoneNumber.createMany({
          data: phoneNumbers.map((pn) => {
            // Find existing entry to preserve sipPassword if not changed
            const existingPn = pn.id
              ? existingPhoneNumbers.find((e) => e.id === pn.id)
              : undefined;

            return {
              internetContractDetailsId: existing.id,
              phoneNumber: sanitizePhoneField(pn.phoneNumber, 'Rufnummer') ?? '',
              areaCode: sanitizePhoneField(pn.areaCode, 'Vorwahl'),
              isMain: pn.isMain ?? false,
              sipUsername: pn.sipUsername,
              // Preserve existing sipPassword if no new value provided
              sipPasswordEncrypted: pn.sipPassword
                ? encrypt(pn.sipPassword)
                : existingPn?.sipPasswordEncrypted ?? undefined,
              sipServer: pn.sipServer,
            };
          }),
        });
      }
    } else {
      await prisma.internetContractDetails.create({
        data: {
          contractId: id,
          ...preparedInternetData,
          ...(internetPassword
            ? { internetPasswordEncrypted: encrypt(internetPassword) }
            : {}),
          phoneNumbers: phoneNumbers
            ? {
                create: phoneNumbers.map((pn) => ({
                  phoneNumber: sanitizePhoneField(pn.phoneNumber, 'Rufnummer') ?? '',
                  areaCode: sanitizePhoneField(pn.areaCode, 'Vorwahl'),
                  isMain: pn.isMain ?? false,
                  sipUsername: pn.sipUsername,
                  sipPasswordEncrypted: pn.sipPassword
                    ? encrypt(pn.sipPassword)
                    : undefined,
                  sipServer: pn.sipServer,
                })),
              }
            : undefined,
        },
      });
    }
  }

  if (mobileDetails) {
    const { simCards } = mobileDetails;
    // Feld-Whitelist (Pentest R162-01): nur Scalars an Prisma – kein injiziertes
    // id/contractId. Danach Netz normalisieren.
    const mobileData: Record<string, unknown> = pickMobileScalars(mobileDetails);
    if ('mobileNetwork' in mobileData) {
      mobileData.mobileNetwork = normalizeMobileNetwork(mobileData.mobileNetwork as string | null | undefined);
    }
    const existing = await prisma.mobileContractDetails.findUnique({
      where: { contractId: id },
    });

    if (existing) {
      await prisma.mobileContractDetails.update({
        where: { contractId: id },
        data: mobileData as any,
      });

      if (simCards) {
        // Get existing sim cards to preserve PIN/PUK if not provided
        const existingSimCards = await prisma.simCard.findMany({
          where: { mobileDetailsId: existing.id },
        });
        const existingSimCardMap = new Map(existingSimCards.map(sc => [sc.id, sc]));

        // Delete existing sim cards
        await prisma.simCard.deleteMany({
          where: { mobileDetailsId: existing.id },
        });

        // Create new sim cards, preserving PIN/PUK if not provided
        await prisma.simCard.createMany({
          data: simCards.map((sc) => {
            const existingSc = sc.id ? existingSimCardMap.get(sc.id) : undefined;
            return {
              mobileDetailsId: existing.id,
              phoneNumber: sc.phoneNumber,
              simCardNumber: sc.simCardNumber,
              // Preserve existing PIN/PUK if no new value provided
              pin: sc.pin ? encrypt(sc.pin) : (existingSc?.pin ?? undefined),
              puk: sc.puk ? encrypt(sc.puk) : (existingSc?.puk ?? undefined),
              isMultisim: sc.isMultisim ?? false,
              isMain: sc.isMain ?? false,
              isEsim: sc.isEsim ?? false,
              cardUser: sc.cardUser,
            };
          }),
        });
      }
    } else {
      await prisma.mobileContractDetails.create({
        data: {
          contractId: id,
          ...(mobileData as any),
          simCards: simCards
            ? {
                create: simCards.map((sc) => ({
                  phoneNumber: sc.phoneNumber,
                  simCardNumber: sc.simCardNumber,
                  pin: sc.pin ? encrypt(sc.pin) : undefined,
                  puk: sc.puk ? encrypt(sc.puk) : undefined,
                  isMultisim: sc.isMultisim ?? false,
                  isMain: sc.isMain ?? false,
                  isEsim: sc.isEsim ?? false,
                  cardUser: sc.cardUser,
                })),
              }
            : undefined,
        },
      });
    }
  }

  if (tvDetails) {
    await prisma.tvContractDetails.upsert({
      where: { contractId: id },
      update: pickTvScalars(tvDetails) as any,
      create: { contractId: id, ...(pickTvScalars(tvDetails) as any) },
    });
  }

  if (carInsuranceDetails) {
    await prisma.carInsuranceDetails.upsert({
      where: { contractId: id },
      update: pickCarInsuranceScalars(carInsuranceDetails) as any,
      create: { contractId: id, ...(pickCarInsuranceScalars(carInsuranceDetails) as any) },
    });
  }

  return getContractById(id);
}

export async function deleteContract(id: number) {
  // Vertragskette erhalten beim Löschen:
  // Wenn A → B → C und B gelöscht wird, soll C direkt auf A zeigen (A → C).
  //
  // Hintergrund: `Contract.previousContractId` hat ein UNIQUE-Constraint
  // (jeder Vertrag darf nur EINEN Vorgänger sein, umgekehrt gilt auch:
  // ein Vorgänger kann nur EINEN Nachfolger haben). Naive Reihenfolge
  // „C erst auf A umbiegen, dann B löschen" scheitert an genau diesem
  // Constraint, weil B in dem Moment noch selbst auf A zeigt →
  // Duplicate-Entry-Fehler (Prisma P2002) und das UPDATE bricht ab.
  // Vorher landete der User dadurch bei einem 400-Fehler, das Cache-
  // Invalidate war ebenfalls nicht sauber (siehe Frontend-Fix), so dass
  // es wie „Vertrag verschwindet aber ist noch da" aussah.
  //
  // Fix: erst B löschen (DB-Cascade `ON DELETE SET NULL` räumt
  // C.previousContractId auf null ab und gibt damit den A-Slot wieder
  // frei), dann C sauber auf A umbiegen. Alles in einer Transaktion,
  // damit kein Zwischenzustand sichtbar wird und ein Fehler die
  // gesamte Operation zurückrollt.
  return prisma.$transaction(async (tx) => {
    const contractToDelete = await tx.contract.findUnique({
      where: { id },
      select: { previousContractId: true },
    });
    const followUp = await tx.contract.findFirst({
      where: { previousContractId: id },
      select: { id: true },
    });

    const deleted = await tx.contract.delete({ where: { id } });

    // Kette schließen, falls sowohl Vorgänger (A) als auch Folgevertrag
    // (C) existieren. Ohne einen der beiden ist nichts zu tun – der
    // verbleibende Endpunkt wird zum neuen Kettenanfang bzw. -ende.
    if (followUp && contractToDelete?.previousContractId) {
      await tx.contract.update({
        where: { id: followUp.id },
        data: { previousContractId: contractToDelete.previousContractId },
      });
    }

    return deleted;
  });
}

export async function createFollowUpContract(previousContractId: number) {
  const previousContract = await getContractById(previousContractId);
  if (!previousContract) {
    throw new Error('Vorgängervertrag nicht gefunden');
  }

  // Prüfen ob bereits ein Folgevertrag existiert
  const existingFollowUp = await prisma.contract.findFirst({
    where: { previousContractId },
    select: { id: true, contractNumber: true },
  });
  if (existingFollowUp) {
    throw new Error(`Es existiert bereits ein Folgevertrag: ${existingFollowUp.contractNumber}`);
  }

  // Copy data but exclude provider credentials and some fields
  const newContractData: ContractCreateData = {
    customerId: previousContract.customerId,
    type: previousContract.type,
    status: 'DRAFT',
    addressId: previousContract.addressId ?? undefined,
    bankCardId: previousContract.bankCardId ?? undefined,
    identityDocumentId: previousContract.identityDocumentId ?? undefined,
    salesPlatformId: previousContract.salesPlatformId ?? undefined,
    previousContractId: previousContract.id,
    // Explicitly NOT copying: providerName, tariffName, portalUsername, portalPassword, price fields
    cancellationPeriodId: previousContract.cancellationPeriodId ?? undefined,
    contractDurationId: previousContract.contractDurationId ?? undefined,
    // notes nicht mehr automatisch setzen - wird jetzt über Historie-Eintrag dokumentiert
  };

  // Copy type-specific details (without credentials)
  if (previousContract.energyDetails) {
    newContractData.energyDetails = {
      meterId: previousContract.energyDetails.meterId ?? undefined,
      annualConsumption:
        previousContract.energyDetails.annualConsumption ?? undefined,
      basePrice: previousContract.energyDetails.basePrice ?? undefined,
      unitPrice: previousContract.energyDetails.unitPrice ?? undefined,
      instantBonus: previousContract.energyDetails.instantBonus ?? undefined,
      newCustomerBonus: previousContract.energyDetails.newCustomerBonus ?? undefined,
      previousProviderName: previousContract.providerName ?? undefined,
      previousCustomerNumber:
        previousContract.customerNumberAtProvider ?? undefined,
    };
  }

  if (previousContract.internetDetails) {
    newContractData.internetDetails = {
      downloadSpeed:
        previousContract.internetDetails.downloadSpeed ?? undefined,
      uploadSpeed: previousContract.internetDetails.uploadSpeed ?? undefined,
      routerModel: previousContract.internetDetails.routerModel ?? undefined,
      routerSerialNumber:
        previousContract.internetDetails.routerSerialNumber ?? undefined,
      phoneNumbers: previousContract.internetDetails.phoneNumbers.map((pn) => ({
        phoneNumber: pn.phoneNumber,
        isMain: pn.isMain,
      })),
    };
  }

  if (previousContract.mobileDetails) {
    newContractData.mobileDetails = {
      requiresMultisim: previousContract.mobileDetails.requiresMultisim ?? undefined,
      dataVolume: previousContract.mobileDetails.dataVolume ?? undefined,
      includedMinutes:
        previousContract.mobileDetails.includedMinutes ?? undefined,
      includedSMS: previousContract.mobileDetails.includedSMS ?? undefined,
      deviceModel: previousContract.mobileDetails.deviceModel ?? undefined,
      deviceImei: previousContract.mobileDetails.deviceImei ?? undefined,
      phoneNumber: previousContract.mobileDetails.phoneNumber ?? undefined,
      simCardNumber: previousContract.mobileDetails.simCardNumber ?? undefined,
      // Copy simCards without PIN/PUK (security)
      simCards: previousContract.mobileDetails.simCards?.map((sc) => ({
        phoneNumber: sc.phoneNumber ?? undefined,
        simCardNumber: sc.simCardNumber ?? undefined,
        isMultisim: sc.isMultisim,
        isMain: sc.isMain,
        isEsim: sc.isEsim,
      })),
    };
  }

  if (previousContract.tvDetails) {
    newContractData.tvDetails = {
      receiverModel: previousContract.tvDetails.receiverModel ?? undefined,
      smartcardNumber:
        previousContract.tvDetails.smartcardNumber ?? undefined,
      package: previousContract.tvDetails.package ?? undefined,
    };
  }

  if (previousContract.carInsuranceDetails) {
    newContractData.carInsuranceDetails = {
      licensePlate:
        previousContract.carInsuranceDetails.licensePlate ?? undefined,
      hsn: previousContract.carInsuranceDetails.hsn ?? undefined,
      tsn: previousContract.carInsuranceDetails.tsn ?? undefined,
      vin: previousContract.carInsuranceDetails.vin ?? undefined,
      vehicleType: previousContract.carInsuranceDetails.vehicleType ?? undefined,
      firstRegistration:
        previousContract.carInsuranceDetails.firstRegistration ?? undefined,
      noClaimsClass:
        previousContract.carInsuranceDetails.noClaimsClass ?? undefined,
      insuranceType: previousContract.carInsuranceDetails.insuranceType,
      deductiblePartial:
        previousContract.carInsuranceDetails.deductiblePartial ?? undefined,
      deductibleFull:
        previousContract.carInsuranceDetails.deductibleFull ?? undefined,
      previousInsurer: previousContract.providerName ?? undefined,
    };
  }

  return createContract(newContractData);
}

/**
 * Hilfsfunktion: extrahiert die Anzahl Monate aus einer ContractDuration.
 * Code-Beispiele: "12M", "24M", "1J", "2J". Falls nichts erkannt wird, fällt
 * sie auf 12 Monate als sicheren Default zurück.
 */
function durationToMonths(code: string | null | undefined, description: string | null | undefined): number {
  const c = (code || '').trim();
  const d = (description || '').trim();
  let m = c.match(/^(\d+)\s*M$/i);
  if (m) return parseInt(m[1], 10);
  m = c.match(/^(\d+)\s*J$/i);
  if (m) return parseInt(m[1], 10) * 12;
  m = d.match(/(\d+)\s*Monat/i);
  if (m) return parseInt(m[1], 10);
  m = d.match(/(\d+)\s*Jahr/i);
  if (m) return parseInt(m[1], 10) * 12;
  return 12;
}

/**
 * VVL = Vertragsverlängerung beim selben Anbieter.
 *
 * Im Gegensatz zu createFollowUpContract werden ALLE Daten 1:1 kopiert:
 * Provider, Tarif, Portal-Credentials, Preise, Notes, ContractDocuments.
 *
 * Berechnet wird das neue Startdatum: altes startDate + Vertragslaufzeit.
 * Stimmt das gefundene Datum nicht mit dem späteren Auftrag überein, kann
 * der User es im Vertrag manuell anpassen.
 *
 * NICHT mitkopiert wird:
 *   - das Auftragsdokument (documentType "Auftragsformular") – das ist
 *     schließlich die NEU zu unterschreibende VVL.
 *   - Kündigungsschreiben/-bestätigung (das war der ALTE Cancel-Flow,
 *     bei einer VVL nicht relevant)
 */
export async function createRenewalContract(previousContractId: number) {
  const previousContract = await getContractById(previousContractId, true);
  if (!previousContract) {
    throw new Error('Vorgängervertrag nicht gefunden');
  }

  // Bereits ein Folge-/VVL-Vertrag vorhanden?
  const existing = await prisma.contract.findFirst({
    where: { previousContractId },
    select: { id: true, contractNumber: true },
  });
  if (existing) {
    throw new Error(`Es existiert bereits ein Folgevertrag: ${existing.contractNumber}`);
  }

  // Neues Startdatum = altes Start + Laufzeit
  let newStartDate: Date | null = null;
  let newEndDate: Date | null = null;
  if (previousContract.startDate && previousContract.contractDuration) {
    const months = durationToMonths(
      previousContract.contractDuration.code,
      previousContract.contractDuration.description,
    );
    newStartDate = new Date(previousContract.startDate);
    newStartDate.setMonth(newStartDate.getMonth() + months);
    newEndDate = new Date(newStartDate);
    newEndDate.setMonth(newEndDate.getMonth() + months);
  }

  // Vertrags-Daten 1:1 kopieren (außer id/contractNumber/Datums-/Cancellation-Felder)
  const contractNumber = generateContractNumber(previousContract.type);

  const newContract = await prisma.contract.create({
    data: {
      contractNumber,
      customerId: previousContract.customerId,
      type: previousContract.type,
      status: 'DRAFT',
      contractCategoryId: previousContract.contractCategoryId,
      addressId: previousContract.addressId,
      billingAddressId: previousContract.billingAddressId,
      bankCardId: previousContract.bankCardId,
      identityDocumentId: previousContract.identityDocumentId,
      salesPlatformId: previousContract.salesPlatformId,
      cancellationPeriodId: previousContract.cancellationPeriodId,
      contractDurationId: previousContract.contractDurationId,
      previousContractId: previousContract.id,
      previousProviderId: previousContract.previousProviderId,
      providerId: previousContract.providerId,
      tariffId: previousContract.tariffId,
      providerName: previousContract.providerName,
      tariffName: previousContract.tariffName,
      customerNumberAtProvider: previousContract.customerNumberAtProvider,
      contractNumberAtProvider: previousContract.contractNumberAtProvider,
      orderNumberAtSalesPlatform: previousContract.orderNumberAtSalesPlatform,
      customerNumberAtSalesPlatform: previousContract.customerNumberAtSalesPlatform,
      contractNumberAtSalesPlatform: previousContract.contractNumberAtSalesPlatform,
      portalUsername: previousContract.portalUsername,
      portalPasswordEncrypted: previousContract.portalPasswordEncrypted,
      commission: previousContract.commission,
      notes: previousContract.notes,
      startDate: newStartDate,
      endDate: newEndDate,
      // Cancellation-Felder bewusst leer lassen – die VVL hat den alten
      // Cancel-Flow nicht geerbt.
    },
  });

  // Detail-Tabellen 1:1 kopieren (id rausnehmen, contractId neu)
  if (previousContract.energyDetails) {
    const ed = previousContract.energyDetails;
    const newEnergy = await prisma.energyContractDetails.create({
      data: {
        contractId: newContract.id,
        meterId: ed.meterId,
        maloId: ed.maloId,
        annualConsumption: ed.annualConsumption,
        annualConsumptionKwh: ed.annualConsumptionKwh,
        basePrice: ed.basePrice,
        unitPrice: ed.unitPrice,
        unitPriceNt: ed.unitPriceNt,
        instantBonus: ed.instantBonus,
        newCustomerBonus: ed.newCustomerBonus,
        previousProviderName: ed.previousProviderName,
        previousCustomerNumber: ed.previousCustomerNumber,
      },
    });
    // ContractMeter-Verknüpfungen mitkopieren
    for (const cm of ed.contractMeters || []) {
      await prisma.contractMeter.create({
        data: {
          energyContractDetailsId: newEnergy.id,
          meterId: cm.meterId,
          position: cm.position,
          installedAt: cm.installedAt,
          removedAt: cm.removedAt,
          finalReading: cm.finalReading,
        },
      });
    }
  }
  if (previousContract.internetDetails) {
    const id = previousContract.internetDetails;
    const newInet = await prisma.internetContractDetails.create({
      data: {
        contractId: newContract.id,
        downloadSpeed: id.downloadSpeed,
        uploadSpeed: id.uploadSpeed,
        routerModel: id.routerModel,
        routerSerialNumber: id.routerSerialNumber,
        installationDate: id.installationDate,
        internetUsername: id.internetUsername,
        internetPasswordEncrypted: id.internetPasswordEncrypted,
        propertyType: id.propertyType,
        propertyLocation: id.propertyLocation,
        connectionLocation: id.connectionLocation,
        homeId: id.homeId,
        activationCode: id.activationCode,
      },
    });
    for (const pn of id.phoneNumbers || []) {
      await prisma.phoneNumber.create({
        data: {
          internetContractDetailsId: newInet.id,
          phoneNumber: pn.phoneNumber,
          isMain: pn.isMain,
          sipUsername: pn.sipUsername,
          sipPasswordEncrypted: pn.sipPasswordEncrypted,
          sipServer: pn.sipServer,
        },
      });
    }
  }
  if (previousContract.mobileDetails) {
    const md = previousContract.mobileDetails;
    const newMob = await prisma.mobileContractDetails.create({
      data: {
        contractId: newContract.id,
        requiresMultisim: md.requiresMultisim,
        dataVolume: md.dataVolume,
        includedMinutes: md.includedMinutes,
        includedSMS: md.includedSMS,
        deviceModel: md.deviceModel,
        deviceImei: md.deviceImei,
        phoneNumber: md.phoneNumber,
        simCardNumber: md.simCardNumber,
      },
    });
    for (const sc of md.simCards || []) {
      await prisma.simCard.create({
        data: {
          mobileDetailsId: newMob.id,
          phoneNumber: sc.phoneNumber,
          simCardNumber: sc.simCardNumber,
          isMultisim: sc.isMultisim,
          isMain: sc.isMain,
          isEsim: sc.isEsim,
          pin: sc.pin,
          puk: sc.puk,
        },
      });
    }
  }
  if (previousContract.tvDetails) {
    await prisma.tvContractDetails.create({
      data: {
        contractId: newContract.id,
        receiverModel: previousContract.tvDetails.receiverModel,
        smartcardNumber: previousContract.tvDetails.smartcardNumber,
        package: previousContract.tvDetails.package,
      },
    });
  }
  if (previousContract.carInsuranceDetails) {
    const ci = previousContract.carInsuranceDetails;
    await prisma.carInsuranceDetails.create({
      data: {
        contractId: newContract.id,
        licensePlate: ci.licensePlate,
        hsn: ci.hsn,
        tsn: ci.tsn,
        vin: ci.vin,
        vehicleType: ci.vehicleType,
        firstRegistration: ci.firstRegistration,
        noClaimsClass: ci.noClaimsClass,
        insuranceType: ci.insuranceType,
        deductiblePartial: ci.deductiblePartial,
        deductibleFull: ci.deductibleFull,
        previousInsurer: ci.previousInsurer,
      },
    });
  }

  // ContractDocuments mitkopieren – AUSSER "Auftragsformular" (das ist die
  // neue Unterschrift, die der User selbst hochlädt). Files werden NICHT
  // physisch dupliziert; beide Verträge zeigen auf dieselbe Datei.
  const docs = await prisma.contractDocument.findMany({
    where: { contractId: previousContract.id },
  });
  for (const d of docs) {
    if (d.documentType.toLowerCase().includes('auftragsformular')) continue;
    await prisma.contractDocument.create({
      data: {
        contractId: newContract.id,
        documentType: d.documentType,
        documentPath: d.documentPath,
        originalName: d.originalName,
        notes: d.notes,
        uploadedBy: d.uploadedBy,
      },
    });
  }

  return prisma.contract.findUnique({ where: { id: newContract.id } });
}

// Decrypt password for viewing
export async function getContractPassword(id: number): Promise<string | null> {
  const contract = await prisma.contract.findUnique({
    where: { id },
    select: { portalPasswordEncrypted: true },
  });

  if (!contract?.portalPasswordEncrypted) return null;

  try {
    return decrypt(contract.portalPasswordEncrypted);
  } catch {
    return null;
  }
}

// Decrypt SimCard PIN/PUK
export async function getSimCardCredentials(simCardId: number): Promise<{ pin: string | null; puk: string | null }> {
  const simCard = await prisma.simCard.findUnique({
    where: { id: simCardId },
    select: { pin: true, puk: true },
  });

  if (!simCard) return { pin: null, puk: null };

  try {
    return {
      pin: simCard.pin ? decrypt(simCard.pin) : null,
      puk: simCard.puk ? decrypt(simCard.puk) : null,
    };
  } catch {
    return { pin: null, puk: null };
  }
}

// Decrypt Internet password
export async function getInternetCredentials(contractId: number): Promise<{ password: string | null }> {
  const internetDetails = await prisma.internetContractDetails.findUnique({
    where: { contractId },
    select: { internetPasswordEncrypted: true },
  });

  if (!internetDetails?.internetPasswordEncrypted) return { password: null };

  try {
    return {
      password: decrypt(internetDetails.internetPasswordEncrypted),
    };
  } catch {
    return { password: null };
  }
}

// Decrypt SIP password for a phone number
export async function getSipCredentials(phoneNumberId: number): Promise<{ password: string | null }> {
  const phoneNumber = await prisma.phoneNumber.findUnique({
    where: { id: phoneNumberId },
    select: { sipPasswordEncrypted: true },
  });

  if (!phoneNumber?.sipPasswordEncrypted) return { password: null };

  try {
    return {
      password: decrypt(phoneNumber.sipPasswordEncrypted),
    };
  } catch {
    return { password: null };
  }
}

// ==================== VERTRAGSBAUM FÜR KUNDENANSICHT ====================

export interface ContractTreeNode {
  contract: {
    id: number;
    contractNumber: string;
    type: ContractType;
    status: ContractStatus;
    startDate: Date | null;
    endDate: Date | null;
    cancellationConfirmationDate: Date | null;
    providerName: string | null;
    tariffName: string | null;
    previousContractId: number | null;
    provider?: { id: number; name: string } | null;
    tariff?: { id: number; name: string } | null;
    contractCategory?: { id: number; name: string } | null;
    customer?: { id: number; firstName: string; lastName: string; companyName: string | null; customerNumber: string } | null;
    address?: { street: string; houseNumber: string; postalCode: string; city: string } | null;
    mobileDetails?: {
      phoneNumber: string | null;
      mobileNetwork: string | null;
      simCards: { phoneNumber: string | null; isMain: boolean; cardUser: string | null }[];
    } | null;
    carInsuranceDetails?: { licensePlate: string | null } | null;
  };
  predecessors: ContractTreeNode[];
  hasHistory: boolean;
}

/**
 * Verträge eines Kunden als Baumstruktur abrufen.
 * Wurzelknoten = Verträge ohne Nachfolger (aktuellste Verträge)
 * Vorgänger werden rekursiv eingebettet.
 */
export async function getContractTreeForCustomer(
  customerId: number,
  includeDeactivated = false,
): Promise<ContractTreeNode[]> {
  // Alle Verträge des Kunden laden. DEACTIVATED standardmäßig ausgeblendet,
  // per Toggle (includeDeactivated) aber sichtbar machen – sonst
  // verschwinden ganze Ketten aus der Ansicht, sobald der aktuellste
  // (Wurzel-)Vertrag deaktiviert ist. Genau dieser Fall trat auf, als ein
  // aktiver Folgevertrag gelöscht wurde und der deaktivierte Vorgänger zur
  // neuen (unsichtbaren) Wurzel wurde.
  const allContracts = await prisma.contract.findMany({
    where: {
      customerId,
      ...(includeDeactivated ? {} : { status: { not: ContractStatus.DEACTIVATED } }),
    },
    select: {
      id: true,
      contractNumber: true,
      type: true,
      status: true,
      startDate: true,
      endDate: true,
      cancellationConfirmationDate: true,
      providerName: true,
      tariffName: true,
      previousContractId: true,
      provider: { select: { id: true, name: true } },
      tariff: { select: { id: true, name: true } },
      contractCategory: { select: { id: true, name: true } },
      customer: { select: { id: true, firstName: true, lastName: true, companyName: true, customerNumber: true } },
      address: { select: { street: true, houseNumber: true, postalCode: true, city: true } },
      mobileDetails: {
        select: {
          phoneNumber: true,
          mobileNetwork: true,
          simCards: { select: { phoneNumber: true, isMain: true, cardUser: true } },
        },
      },
      carInsuranceDetails: { select: { licensePlate: true } },
    },
    orderBy: [{ startDate: 'desc' }, { createdAt: 'desc' }],
  });

  // Map für schnellen Zugriff: contractId -> contract
  const contractMap = new Map(allContracts.map(c => [c.id, c]));

  // Set der IDs die als Vorgänger referenziert werden
  const predecessorIds = new Set(
    allContracts
      .filter(c => c.previousContractId !== null)
      .map(c => c.previousContractId!)
  );

  // Wurzelverträge = Verträge die keinen Nachfolger haben
  // (werden von keinem anderen Vertrag als previousContractId referenziert)
  const rootContracts = allContracts.filter(c => !predecessorIds.has(c.id));

  // Rekursive Funktion um Vorgängerkette aufzubauen
  function buildPredecessorChain(contractId: number | null): ContractTreeNode[] {
    if (contractId === null) return [];

    const contract = contractMap.get(contractId);
    if (!contract) return [];

    const predecessors = buildPredecessorChain(contract.previousContractId);

    return [{
      contract,
      predecessors,
      hasHistory: predecessors.length > 0,
    }];
  }

  // Baumstruktur für jeden Wurzelvertrag aufbauen
  const tree: ContractTreeNode[] = rootContracts.map(contract => {
    const predecessors = buildPredecessorChain(contract.previousContractId);
    return {
      contract,
      predecessors,
      hasHistory: predecessors.length > 0,
    };
  });

  return tree;
}
