import { CustomerType, ContractStatus } from '@prisma/client';
import prisma from '../lib/prisma.js';
import { generateCustomerNumber, paginate, buildPaginationResponse } from '../utils/helpers.js';
import { ApiError } from '../utils/apiError.js';
import { deleteUploadByRelativePath } from '../utils/fileCleanup.js';

export interface CustomerFilters {
  search?: string;
  type?: CustomerType;
  page?: number;
  limit?: number;
  // Wenn gesetzt: nur Customer mit id in dieser Liste. Für Portal-User, damit
  // weder Liste noch pagination.total die globale Kunden-Zahl preisgibt.
  allowedIds?: number[];
}

export async function getAllCustomers(filters: CustomerFilters) {
  const { search, type, page = 1, limit = 20, allowedIds } = filters;
  const { skip, take } = paginate(page, limit);

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

  if (type) {
    where.type = type;
  }

  if (allowedIds) {
    where.id = { in: allowedIds };
  }

  if (search) {
    where.OR = [
      { firstName: { contains: search } },
      { lastName: { contains: search } },
      { companyName: { contains: search } },
      { email: { contains: search } },
      { customerNumber: { contains: search } },
    ];
  }

  const [customers, total] = await Promise.all([
    prisma.customer.findMany({
      where,
      skip,
      take,
      orderBy: { createdAt: 'desc' },
      include: {
        addresses: { where: { isDefault: true }, take: 1 },
        _count: {
          select: { contracts: true },
        },
      },
    }),
    prisma.customer.count({ where }),
  ]);

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

export async function getCustomerById(id: number) {
  return prisma.customer.findUnique({
    where: { id },
    include: {
      addresses: true,
      bankCards: { orderBy: { isActive: 'desc' } },
      identityDocuments: { orderBy: { isActive: 'desc' } },
      meters: {
        orderBy: { isActive: 'desc' },
        include: {
          address: true,
          readings: {
            orderBy: { readingDate: 'desc' },
          },
          // Verträge, die diesen Zähler aktuell als Hauptzähler nutzen
          // (energyDetails.meterId === meter.id)
          energyDetails: {
            include: {
              contract: { select: { id: true, contractNumber: true, status: true, type: true, providerName: true } },
            },
          },
          // Verträge, in denen der Zähler in der ContractMeter-Kette steht
          // (Vorgänger oder Nachfolger über Zählerwechsel)
          contractMeters: {
            include: {
              energyContractDetails: {
                include: {
                  contract: { select: { id: true, contractNumber: true, status: true, type: true, providerName: true } },
                },
              },
            },
          },
        },
      },
      stressfreiEmails: { orderBy: { isActive: 'desc' } },
      contracts: {
        where: {
          // Deaktivierte Verträge ausblenden
          status: { not: ContractStatus.DEACTIVATED },
        },
        orderBy: [{ startDate: 'desc' }, { createdAt: 'desc' }],
        include: {
          address: true,
          salesPlatform: true,
        },
      },
    },
  });
}

export async function getCustomersByIds(ids: number[]) {
  return prisma.customer.findMany({
    where: { id: { in: ids } },
    select: {
      id: true,
      portalEmail: true,
    },
  });
}

export async function createCustomer(data: {
  type?: CustomerType;
  salutation?: string;
  firstName: string;
  lastName: string;
  companyName?: string;
  birthDate?: Date;
  birthPlace?: string;
  email?: string;
  phone?: string;
  mobile?: string;
  taxNumber?: string;
  vatExempt?: boolean;
  businessRegistration?: string;
  commercialRegister?: string;
  notes?: string;
}) {
  return prisma.customer.create({
    data: {
      ...data,
      customerNumber: generateCustomerNumber(),
    },
  });
}

export async function updateCustomer(
  id: number,
  data: {
    type?: CustomerType;
    salutation?: string;
    useInformalAddress?: boolean;
    firstName?: string;
    lastName?: string;
    companyName?: string;
    birthDate?: Date;
    birthPlace?: string;
    email?: string;
    phone?: string;
    mobile?: string;
    taxNumber?: string;
    vatExempt?: boolean;
    businessRegistration?: string;
    commercialRegister?: string;
    notes?: string;
    autoBirthdayGreeting?: boolean;
    autoBirthdayChannel?: string | null;
  }
) {
  return prisma.customer.update({
    where: { id },
    data,
  });
}

export async function deleteCustomer(id: number) {
  // Vor dem Löschen: Alle Dokumente (Dateien) des Kunden löschen
  const customer = await prisma.customer.findUnique({
    where: { id },
    select: { businessRegistrationPath: true, commercialRegisterPath: true, privacyPolicyPath: true },
  });
  const bankCards = await prisma.bankCard.findMany({
    where: { customerId: id },
    select: { documentPath: true },
  });
  const identityDocs = await prisma.identityDocument.findMany({
    where: { customerId: id },
    select: { documentPath: true },
  });

  // Kundendokumente löschen
  if (customer) {
    deleteUploadByRelativePath(customer.businessRegistrationPath);
    deleteUploadByRelativePath(customer.commercialRegisterPath);
    deleteUploadByRelativePath(customer.privacyPolicyPath);
  }

  // Bankkarten- und Ausweisdokumente löschen
  for (const card of bankCards) {
    deleteUploadByRelativePath(card.documentPath);
  }
  for (const doc of identityDocs) {
    deleteUploadByRelativePath(doc.documentPath);
  }

  // Jetzt DB-Eintrag löschen (Cascade löscht die verknüpften Einträge)
  return prisma.customer.delete({
    where: { id },
  });
}

// Address operations
export async function getCustomerAddresses(customerId: number) {
  return prisma.address.findMany({
    where: { customerId },
    orderBy: [{ isDefault: 'desc' }, { createdAt: 'desc' }],
  });
}

// Feld-Whitelist für Address-Create/Update (Mass-Assignment-Schutz, konsistent
// zu R156; autoritativ aus den DB-Spalten abgeleitet). BEWUSST NICHT enthalten:
// id/customerId/createdAt/updatedAt. Enthält die Eigentümer-Felder (owner*) –
// die waren beim ersten R156-Umbau versehentlich rausgefallen (Regression) –
// sowie die neuen MaLo-IDs.
const ADDRESS_SCALAR_FIELDS = [
  'type', 'street', 'houseNumber', 'postalCode', 'city', 'country', 'isDefault',
  'maloIdElectricity', 'maloIdGas',
  'ownerCompany', 'ownerFirstName', 'ownerLastName', 'ownerStreet', 'ownerHouseNumber',
  'ownerPostalCode', 'ownerCity', 'ownerPhone', 'ownerMobile', 'ownerEmail',
] as const;

function pickAddressScalars(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 ADDRESS_SCALAR_FIELDS) {
    if (key in src) out[key] = src[key];
  }
  return out;
}

export async function createAddress(
  customerId: number,
  data: {
    type: 'DELIVERY_RESIDENCE' | 'BILLING';
    street: string;
    houseNumber: string;
    postalCode: string;
    city: string;
    country?: string;
    isDefault?: boolean;
  }
) {
  // If this is set as default, unset other defaults of same type
  if (data.isDefault) {
    await prisma.address.updateMany({
      where: { customerId, type: data.type },
      data: { isDefault: false },
    });
  }

  // Feld-Whitelist (Mass-Assignment-Schutz) inkl. owner*/MaLo – siehe
  // ADDRESS_SCALAR_FIELDS. customerId kommt ausschließlich aus dem Parameter.
  return prisma.address.create({
    data: {
      customerId,
      ...(pickAddressScalars(data) as any),
    },
  });
}

export async function updateAddress(
  id: number,
  data: {
    type?: 'DELIVERY_RESIDENCE' | 'BILLING';
    street?: string;
    houseNumber?: string;
    postalCode?: string;
    city?: string;
    country?: string;
    isDefault?: boolean;
  }
) {
  const address = await prisma.address.findUnique({ where: { id } });
  if (!address) throw new Error('Adresse nicht gefunden');

  if (data.isDefault) {
    await prisma.address.updateMany({
      where: {
        customerId: address.customerId,
        type: data.type || address.type,
        id: { not: id },
      },
      data: { isDefault: false },
    });
  }

  // Feld-Whitelist (Mass-Assignment-Schutz) inkl. owner*/MaLo – siehe
  // ADDRESS_SCALAR_FIELDS.
  return prisma.address.update({
    where: { id },
    data: pickAddressScalars(data) as any,
  });
}

export async function deleteAddress(id: number) {
  return prisma.address.delete({ where: { id } });
}

// Bank card operations
export async function getCustomerBankCards(
  customerId: number,
  showInactive: boolean = false
) {
  const where: Record<string, unknown> = { customerId };
  if (!showInactive) {
    where.isActive = true;
  }
  return prisma.bankCard.findMany({
    where,
    orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }],
  });
}

export async function createBankCard(
  customerId: number,
  data: {
    accountHolder: string;
    iban: string;
    cardNumber?: string;
    bic?: string;
    bankName?: string;
    expiryDate?: Date;
    description?: string;
  }
) {
  // Explizite Feld-Whitelist statt `...data`-Spread: verhindert Mass-Assignment
  // (Pentest R155). Sonst könnte roher req.body u.a. `customerId`/`id` setzen.
  return prisma.bankCard.create({
    data: {
      customerId,
      accountHolder: data.accountHolder,
      iban: data.iban,
      cardNumber: data.cardNumber,
      bic: data.bic,
      bankName: data.bankName,
      expiryDate: data.expiryDate,
      description: data.description,
      isActive: true,
    },
  });
}

export async function updateBankCard(
  id: number,
  data: {
    accountHolder?: string;
    iban?: string;
    cardNumber?: string;
    bic?: string;
    bankName?: string;
    expiryDate?: Date;
    description?: string;
    isActive?: boolean;
  }
) {
  // Explizite Feld-Whitelist statt `data`-Passthrough: verhindert Mass-
  // Assignment (Pentest R155 – roher req.body konnte `customerId`/`id`
  // mutieren). Undefined-Felder ignoriert Prisma → Partial-Update bleibt.
  return prisma.bankCard.update({
    where: { id },
    data: {
      accountHolder: data.accountHolder,
      iban: data.iban,
      cardNumber: data.cardNumber,
      bic: data.bic,
      bankName: data.bankName,
      expiryDate: data.expiryDate,
      description: data.description,
      isActive: data.isActive,
    },
  });
}

export async function deleteBankCard(id: number) {
  // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen
  const bankCard = await prisma.bankCard.findUnique({ where: { id } });
  if (bankCard?.documentPath) {
    deleteUploadByRelativePath(bankCard.documentPath);
  }
  return prisma.bankCard.delete({ where: { id } });
}

// Identity document operations
export async function getCustomerDocuments(
  customerId: number,
  showInactive: boolean = false
) {
  const where: Record<string, unknown> = { customerId };
  if (!showInactive) {
    where.isActive = true;
  }
  return prisma.identityDocument.findMany({
    where,
    orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }],
  });
}

export async function createDocument(
  customerId: number,
  data: {
    type: 'ID_CARD' | 'PASSPORT' | 'DRIVERS_LICENSE' | 'OTHER';
    documentNumber: string;
    issuingAuthority?: string;
    issueDate?: Date;
    expiryDate?: Date;
    licenseClasses?: string;
    licenseIssueDate?: Date;
  }
) {
  // Explizite Feld-Whitelist (Mass-Assignment-Schutz, Pentest R155).
  return prisma.identityDocument.create({
    data: {
      customerId,
      type: data.type,
      documentNumber: data.documentNumber,
      issuingAuthority: data.issuingAuthority,
      issueDate: data.issueDate,
      expiryDate: data.expiryDate,
      licenseClasses: data.licenseClasses,
      licenseIssueDate: data.licenseIssueDate,
      isActive: true,
    },
  });
}

export async function updateDocument(
  id: number,
  data: {
    type?: 'ID_CARD' | 'PASSPORT' | 'DRIVERS_LICENSE' | 'OTHER';
    documentNumber?: string;
    issuingAuthority?: string;
    issueDate?: Date;
    expiryDate?: Date;
    licenseClasses?: string;
    licenseIssueDate?: Date;
    isActive?: boolean;
  }
) {
  // Explizite Feld-Whitelist (Mass-Assignment-Schutz, Pentest R155).
  return prisma.identityDocument.update({
    where: { id },
    data: {
      type: data.type,
      documentNumber: data.documentNumber,
      issuingAuthority: data.issuingAuthority,
      issueDate: data.issueDate,
      expiryDate: data.expiryDate,
      licenseClasses: data.licenseClasses,
      licenseIssueDate: data.licenseIssueDate,
      isActive: data.isActive,
    },
  });
}

export async function deleteDocument(id: number) {
  // Erst Datei-Pfad holen, dann Datei löschen, dann DB-Eintrag löschen
  const document = await prisma.identityDocument.findUnique({ where: { id } });
  if (document?.documentPath) {
    deleteUploadByRelativePath(document.documentPath);
  }
  return prisma.identityDocument.delete({ where: { id } });
}

// Meter operations
export async function getCustomerMeters(
  customerId: number,
  showInactive: boolean = false
) {
  const where: Record<string, unknown> = { customerId };
  if (!showInactive) {
    where.isActive = true;
  }
  return prisma.meter.findMany({
    where,
    include: {
      address: true,
      readings: {
        orderBy: { readingDate: 'desc' },
        take: 5,
      },
    },
    orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }],
  });
}

// Schreibt den Endstand des Vorgänger-Zählers beim Zählerwechsel als
// MeterReading. Wird beim Folgezähler-Anlegen aufgerufen (sowohl aus der
// Kundenakte als auch aus der Vertragsansicht). Idempotent: existiert am
// Wechseltag schon ein Reading, wird nichts angelegt. Validierung
// monoton-steigend wird durchgereicht – wirft bei Konflikt.
export async function recordPredecessorFinalReading(
  predecessorMeterId: number,
  switchAt: Date,
  value: number,
) {
  const meter = await prisma.meter.findUnique({
    where: { id: predecessorMeterId },
    select: { type: true },
  });
  if (!meter) return;

  const dayStart = new Date(switchAt);
  dayStart.setHours(0, 0, 0, 0);
  const dayEnd = new Date(dayStart);
  dayEnd.setDate(dayEnd.getDate() + 1);

  const existingSameDay = await prisma.meterReading.findFirst({
    where: { meterId: predecessorMeterId, readingDate: { gte: dayStart, lt: dayEnd } },
  });
  if (existingSameDay) return;

  await validateReadingValue(predecessorMeterId, switchAt, value, undefined, 'HT');

  await prisma.meterReading.create({
    data: {
      meterId: predecessorMeterId,
      readingDate: switchAt,
      value,
      unit: meter.type === 'GAS' ? 'm³' : 'kWh',
      notes: 'Endstand bei Zählerwechsel (automatisch beim Folgezähler-Anlegen erfasst)',
    },
  });
}

// Lieferadresse muss zum Kunden gehören und vom Typ DELIVERY_RESIDENCE sein.
// Wirft eine sprechende Fehlermeldung, die der Controller dem User durchreicht.
async function assertDeliveryAddressBelongsToCustomer(addressId: number, customerId: number) {
  const addr = await prisma.address.findUnique({ where: { id: addressId } });
  if (!addr || addr.customerId !== customerId) {
    throw new Error('Ungültige Lieferadresse');
  }
  if (addr.type !== 'DELIVERY_RESIDENCE') {
    throw new Error('Nur Lieferadressen können einem Zähler zugeordnet werden');
  }
}

export async function createMeter(
  customerId: number,
  data: {
    meterNumber: string;
    type: 'ELECTRICITY' | 'GAS';
    tariffModel?: 'SINGLE' | 'DUAL';
    location?: string;
    addressId?: number | null;
    // Optional: dieser Zähler ersetzt einen bestehenden (Folgezähler).
    // Beim Create werden alle Verträge, die den Vorgänger als aktuellen
    // Zähler nutzen, automatisch auf den neuen Zähler umgestellt
    // (ContractMeter-Eintrag analog zu Vertragsansicht).
    successorOf?: {
      predecessorMeterId: number;
      installedAt?: string;
      finalReadingPrevious?: number;
      // Default true im UI: alter Zähler wird nach dem Wechsel auf
      // isActive=false gesetzt. Kann ausgeschaltet werden, wenn der alte
      // Zähler aus irgendeinem Grund noch aktiv bleiben soll.
      deactivatePredecessor?: boolean;
    };
  }
) {
  if (data.addressId == null) {
    throw new Error('Lieferadresse ist erforderlich');
  }
  await assertDeliveryAddressBelongsToCustomer(data.addressId, customerId);

  // Vorgänger validieren (wenn Folgezähler)
  let predecessor: { id: number; customerId: number; type: 'ELECTRICITY' | 'GAS' } | null = null;
  if (data.successorOf) {
    const pred = await prisma.meter.findUnique({
      where: { id: data.successorOf.predecessorMeterId },
      select: { id: true, customerId: true, type: true },
    });
    if (!pred || pred.customerId !== customerId) {
      throw new Error('Vorgänger-Zähler nicht gefunden');
    }
    if (pred.type !== data.type) {
      throw new Error('Vorgänger-Zähler muss denselben Typ haben (Strom/Gas)');
    }
    predecessor = pred;

    // Endstand bereits hier validieren, damit kein verwaister Meter entsteht
    // wenn der Wert mit bestehenden Zählerständen kollidiert.
    if (data.successorOf.finalReadingPrevious != null) {
      const switchAt = data.successorOf.installedAt
        ? new Date(data.successorOf.installedAt)
        : new Date();
      await validateReadingValue(
        pred.id,
        switchAt,
        data.successorOf.finalReadingPrevious,
        undefined,
        'HT',
      );
    }
  }

  const created = await prisma.meter.create({
    data: {
      customerId,
      meterNumber: data.meterNumber,
      type: data.type,
      tariffModel: data.tariffModel,
      location: data.location,
      addressId: data.addressId,
      isActive: true,
      predecessorMeterId: predecessor?.id,
    },
    include: { address: true, predecessor: true },
  });

  // Folgezähler-Propagation: alle Verträge, die den Vorgänger als aktuellen
  // Zähler nutzen, bekommen den neuen Zähler als Nachfolger angehängt
  // (analog zu addSuccessorMeter im contract.controller).
  if (predecessor && data.successorOf) {
    const installedAt = data.successorOf.installedAt
      ? new Date(data.successorOf.installedAt)
      : new Date();
    const finalReading = data.successorOf.finalReadingPrevious;

    const affectedContracts = await prisma.energyContractDetails.findMany({
      where: { meterId: predecessor.id },
      include: { contractMeters: { orderBy: { position: 'asc' } } },
    });

    for (const ecd of affectedContracts) {
      // Vorhandenen ContractMeter für den Vorgänger als gewechselt markieren.
      // Falls noch kein ContractMeter für den Vorgänger existiert (Single-Meter-
      // Vertrag vor Multi-Meter-Refactor), legen wir ihn als position 0 an,
      // damit die Kette lückenlos ist.
      let predCM = ecd.contractMeters.find((cm) => cm.meterId === predecessor!.id);
      if (!predCM) {
        predCM = await prisma.contractMeter.create({
          data: {
            energyContractDetailsId: ecd.id,
            meterId: predecessor.id,
            position: 0,
            installedAt: null,
          },
        });
        ecd.contractMeters.push(predCM);
      }
      await prisma.contractMeter.update({
        where: { id: predCM.id },
        data: {
          removedAt: installedAt,
          finalReading: finalReading != null ? finalReading : predCM.finalReading,
        },
      });

      const nextPosition = ecd.contractMeters.length > 0
        ? Math.max(...ecd.contractMeters.map((cm) => cm.position)) + 1
        : 0;

      // Idempotenz: falls (durch Doppel-Klick o.ä.) schon ein ContractMeter
      // mit dem neuen Zähler existiert, nicht doppelt anlegen.
      const existsForNew = await prisma.contractMeter.findUnique({
        where: {
          energyContractDetailsId_meterId: {
            energyContractDetailsId: ecd.id,
            meterId: created.id,
          },
        },
      });
      if (!existsForNew) {
        await prisma.contractMeter.create({
          data: {
            energyContractDetailsId: ecd.id,
            meterId: created.id,
            position: nextPosition,
            installedAt,
          },
        });
      }

      // Aktuellen Zähler am Vertrag aktualisieren
      await prisma.energyContractDetails.update({
        where: { id: ecd.id },
        data: { meterId: created.id },
      });
    }

    // Endstand des Vorgängers als regulären Zählerstand erfassen, damit er
    // in die Verbrauchsberechnung einfließt und in der Zählerstände-Liste
    // sichtbar ist. Idempotent gegen Doppel-Submit.
    if (data.successorOf.finalReadingPrevious != null) {
      await recordPredecessorFinalReading(
        predecessor.id,
        installedAt,
        data.successorOf.finalReadingPrevious,
      );
    }

    // Alten Zähler deaktivieren (Default), sofern der Aufrufer das nicht
    // explizit auf false setzt. Macht den typischen Zählerwechsel-Workflow
    // ein-klick-fähig.
    if (data.successorOf.deactivatePredecessor !== false) {
      await prisma.meter.update({
        where: { id: predecessor.id },
        data: { isActive: false },
      });
    }
  }

  return created;
}

export async function updateMeter(
  id: number,
  data: {
    meterNumber?: string;
    type?: 'ELECTRICITY' | 'GAS';
    tariffModel?: 'SINGLE' | 'DUAL';
    location?: string;
    isActive?: boolean;
    addressId?: number | null;
  }
) {
  if (data.addressId !== undefined && data.addressId !== null) {
    const meter = await prisma.meter.findUnique({ where: { id }, select: { customerId: true } });
    if (!meter) throw new Error('Zähler nicht gefunden');
    await assertDeliveryAddressBelongsToCustomer(data.addressId, meter.customerId);
  }
  return prisma.meter.update({
    where: { id },
    data,
    include: { address: true },
  });
}

export async function deleteMeter(id: number) {
  // Prüfen ob der Zähler noch an Verträgen hängt
  const linkedContracts = await prisma.contractMeter.findMany({
    where: { meterId: id },
    include: { energyContractDetails: { include: { contract: { select: { contractNumber: true } } } } },
  });

  if (linkedContracts.length > 0) {
    const contractNumbers = linkedContracts
      .map(cm => cm.energyContractDetails.contract.contractNumber)
      .join(', ');
    throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`);
  }

  // Auch direkte meterId-Referenz auf EnergyContractDetails prüfen
  const directLinks = await prisma.energyContractDetails.findMany({
    where: { meterId: id },
    include: { contract: { select: { contractNumber: true } } },
  });

  if (directLinks.length > 0) {
    const contractNumbers = directLinks.map(d => d.contract.contractNumber).join(', ');
    throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`);
  }

  return prisma.meter.delete({ where: { id } });
}

export async function addMeterReading(
  meterId: number,
  data: {
    readingDate: Date;
    value: number;
    valueNt?: number;
    unit?: string;
    notes?: string;
  }
) {
  // Validierung: Zählerstand muss monoton steigend sein
  await validateReadingValue(meterId, data.readingDate, data.value, undefined, 'HT');
  if (data.valueNt !== undefined) {
    await validateReadingValue(meterId, data.readingDate, data.valueNt, undefined, 'NT');
  }

  return prisma.meterReading.create({
    data: {
      meterId,
      readingDate: data.readingDate,
      value: data.value,
      valueNt: data.valueNt,
      unit: data.unit,
      notes: data.notes,
    },
  });
}

export async function getMeterReadings(meterId: number) {
  return prisma.meterReading.findMany({
    where: { meterId },
    orderBy: { readingDate: 'desc' },
  });
}

export async function updateMeterReading(
  meterId: number,
  readingId: number,
  data: {
    readingDate?: Date;
    value?: number;
    valueNt?: number | null;
    unit?: string;
    notes?: string;
  }
) {
  // Verify the reading belongs to the meter
  const reading = await prisma.meterReading.findFirst({
    where: { id: readingId, meterId },
  });

  if (!reading) {
    throw new Error('Zählerstand nicht gefunden');
  }

  // Validierung bei Wertänderung
  if (data.value !== undefined || data.readingDate !== undefined) {
    await validateReadingValue(
      meterId,
      data.readingDate || reading.readingDate,
      data.value ?? reading.value,
      readingId,
      'HT'
    );
  }
  if (data.valueNt !== undefined || data.readingDate !== undefined) {
    const ntVal = data.valueNt ?? reading.valueNt;
    if (ntVal !== undefined && ntVal !== null) {
      await validateReadingValue(
        meterId,
        data.readingDate || reading.readingDate,
        ntVal,
        readingId,
        'NT'
      );
    }
  }

  return prisma.meterReading.update({
    where: { id: readingId },
    data,
  });
}

/**
 * Validiert, dass ein Zählerstand monoton steigend ist.
 * tariffLabel: 'HT' für Hochtarif/Eintarif, 'NT' für Niedertarif
 */
async function validateReadingValue(meterId: number, readingDate: Date, value: number, excludeReadingId?: number, tariffLabel: 'HT' | 'NT' = 'HT') {
  const existing = await prisma.meterReading.findMany({
    where: { meterId, ...(excludeReadingId ? { id: { not: excludeReadingId } } : {}) },
    orderBy: { readingDate: 'asc' },
  });

  const fmtDate = (d: Date) => d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
  const fmtVal = (v: number) => v.toLocaleString('de-DE');
  const label = tariffLabel === 'NT' ? 'NT-Zählerstand' : 'Zählerstand';

  // Vergleichswert aus bestehendem Reading extrahieren
  const getVal = (r: typeof existing[0]) => tariffLabel === 'NT' ? (r.valueNt ?? 0) : r.value;

  // Stand vor dem neuen Datum
  const before = [...existing].filter(r => r.readingDate <= readingDate).pop();
  if (before && value < getVal(before)) {
    throw new Error(`${label} (${fmtVal(value)}) darf nicht kleiner sein als der Stand vom ${fmtDate(before.readingDate)} (${fmtVal(getVal(before))})`);
  }

  // Stand nach dem neuen Datum
  const after = existing.find(r => r.readingDate > readingDate);
  if (after && value > getVal(after)) {
    throw new Error(`${label} (${fmtVal(value)}) darf nicht größer sein als der spätere Stand vom ${fmtDate(after.readingDate)} (${fmtVal(getVal(after))})`);
  }
}

export async function deleteMeterReading(meterId: number, readingId: number) {
  // Verify the reading belongs to the meter
  const reading = await prisma.meterReading.findFirst({
    where: { id: readingId, meterId },
  });

  if (!reading) {
    throw new Error('Zählerstand nicht gefunden');
  }

  return prisma.meterReading.delete({
    where: { id: readingId },
  });
}

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

export async function updatePortalSettings(
  customerId: number,
  data: {
    portalEnabled?: boolean;
    portalEmail?: string | null;
  }
) {
  // Wenn Portal deaktiviert wird, Passwort-Hash nicht löschen (für spätere Reaktivierung)
  return prisma.customer.update({
    where: { id: customerId },
    data: {
      portalEnabled: data.portalEnabled,
      portalEmail: data.portalEmail,
    },
    select: {
      id: true,
      portalEnabled: true,
      portalEmail: true,
      portalLastLogin: true,
    },
  });
}

export async function getPortalSettings(customerId: number) {
  return prisma.customer.findUnique({
    where: { id: customerId },
    select: {
      id: true,
      portalEnabled: true,
      portalEmail: true,
      portalLastLogin: true,
      portalPasswordHash: true, // Nur um zu prüfen ob Passwort gesetzt (wird als boolean zurückgegeben)
    },
  });
}

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

export async function getCustomerRepresentatives(customerId: number) {
  // Holt alle Kunden, die der angegebene Kunde vertreten kann (dieser ist der Vertreter)
  return prisma.customerRepresentative.findMany({
    where: { representativeId: customerId, isActive: true },
    include: {
      customer: {
        select: {
          id: true,
          customerNumber: true,
          firstName: true,
          lastName: true,
          companyName: true,
          type: true,
        },
      },
    },
    orderBy: { createdAt: 'desc' },
  });
}

export async function getRepresentedByList(customerId: number) {
  // Holt alle Kunden, die den angegebenen Kunden vertreten können.
  // `portalEnabled` mit rausgeben, damit die UI ein Badge zeigen kann,
  // falls der Vertreter noch keinen Portal-Zugang hat (Vertretung ist
  // dann formal eingetragen, greift aber erst mit aktivem Portal).
  return prisma.customerRepresentative.findMany({
    where: { customerId: customerId, isActive: true },
    include: {
      representative: {
        select: {
          id: true,
          customerNumber: true,
          firstName: true,
          lastName: true,
          companyName: true,
          type: true,
          portalEnabled: true,
        },
      },
    },
    orderBy: { createdAt: 'desc' },
  });
}

export async function addRepresentative(
  customerId: number, // Der Kunde, dessen Verträge eingesehen werden dürfen
  representativeId: number, // Der Kunde, der einsehen darf
  notes?: string
) {
  // Prüfen, ob beide Kunden existieren
  const [customer, representative] = await Promise.all([
    prisma.customer.findUnique({ where: { id: customerId } }),
    prisma.customer.findUnique({ where: { id: representativeId } }),
  ]);

  if (!customer) {
    throw new Error('Kunde nicht gefunden');
  }
  if (!representative) {
    throw new Error('Vertreter-Kunde nicht gefunden');
  }
  if (customerId === representativeId) {
    throw new Error('Ein Kunde kann sich nicht selbst vertreten');
  }

  // Portal-Zwang entfernt (2026-07-03): der Vertreter darf ohne Portal
  // eingetragen werden; die Beziehung ist erst dann effektiv aktiv,
  // wenn der Vertreter einen Portal-Zugang bekommt (die Frontend-UI
  // zeigt in dem Fall ein Warn-Badge).

  return prisma.customerRepresentative.upsert({
    where: {
      customerId_representativeId: { customerId, representativeId },
    },
    create: {
      customerId,
      representativeId,
      notes,
      isActive: true,
    },
    update: {
      isActive: true,
      notes,
    },
    include: {
      representative: {
        select: {
          id: true,
          customerNumber: true,
          firstName: true,
          lastName: true,
          companyName: true,
          type: true,
        },
      },
    },
  });
}

export async function removeRepresentative(customerId: number, representativeId: number) {
  // Anstatt zu löschen, setzen wir isActive auf false
  return prisma.customerRepresentative.update({
    where: {
      customerId_representativeId: { customerId, representativeId },
    },
    data: { isActive: false },
  });
}

export async function searchCustomersForRepresentative(search: string, excludeCustomerId: number) {
  // Sucht Kunden, die als Vertreter hinzugefügt werden können.
  // 2026-07-03: Portal-Filter entfernt – der Admin soll auch Kunden ohne
  // Portal-Zugang als Vertreter markieren können; das Portal wird dann
  // typischerweise nachträglich aktiviert. Der Zugriff wird ohnehin erst
  // aktiv, sobald das Portal für den Vertreter angeschaltet ist – die UI
  // zeigt jetzt ein Warn-Badge, wenn der Vertreter noch kein Portal hat.
  return prisma.customer.findMany({
    where: {
      id: { not: excludeCustomerId },
      OR: [
        { firstName: { contains: search } },
        { lastName: { contains: search } },
        { companyName: { contains: search } },
        { customerNumber: { contains: search } },
      ],
    },
    select: {
      id: true,
      customerNumber: true,
      firstName: true,
      lastName: true,
      companyName: true,
      type: true,
      portalEnabled: true,
    },
    take: 10,
  });
}

// ==================== SALUTATION PREFERENCE ====================
// Per-User "Du"/"Sie" pro Kunde. Fehlender Eintrag ⇒ Kunden-Default
// (Customer.useInformalAddress). Nur Mitarbeiter-User, keine Portal-User.

export type SalutationPreference = 'DU' | 'SIE';

function isValidPreference(v: unknown): v is SalutationPreference {
  return v === 'DU' || v === 'SIE';
}

/**
 * Wirft ApiError(404), wenn kein Kunde mit dieser ID existiert. Vor
 * jedem Salutation-Preference-Zugriff aufrufen – ohne den Check würde
 * `upsert`/`deleteMany` einen Prisma-FK-Error werfen, den der Controller
 * als 500 mappt (Pentest R104.1).
 */
async function assertCustomerExists(customerId: number): Promise<void> {
  const exists = await prisma.customer.findUnique({
    where: { id: customerId },
    select: { id: true },
  });
  if (!exists) {
    throw new ApiError(404, 'Kunde nicht gefunden');
  }
}

/**
 * Liefert die effektive Anrede für einen User/Kunde: eigene Präferenz
 * bevorzugt, sonst Kunden-Default. `source` sagt der UI, ob's ein eigener
 * Override ist oder der Fallback greift.
 */
export async function getSalutationPreference(
  userId: number,
  customerId: number,
): Promise<{
  preference: SalutationPreference;
  source: 'user' | 'customer-default';
}> {
  const own = await prisma.userCustomerSalutation.findUnique({
    where: { userId_customerId: { userId, customerId } },
  });
  if (own && isValidPreference(own.preference)) {
    return { preference: own.preference, source: 'user' };
  }
  const customer = await prisma.customer.findUnique({
    where: { id: customerId },
    select: { useInformalAddress: true },
  });
  if (!customer) {
    // Pentest R104.1 (INFO, 2026-07-04): vorher `throw new Error(...)`,
    // was der Controller-Catch als 500 mappte. Sauberes 404 macht die
    // Response semantisch korrekt (existiert nicht / gerade gelöscht).
    throw new ApiError(404, 'Kunde nicht gefunden');
  }
  return {
    preference: customer.useInformalAddress ? 'DU' : 'SIE',
    source: 'customer-default',
  };
}

export async function setSalutationPreference(
  userId: number,
  customerId: number,
  preference: SalutationPreference,
) {
  await assertCustomerExists(customerId);
  return prisma.userCustomerSalutation.upsert({
    where: { userId_customerId: { userId, customerId } },
    create: { userId, customerId, preference },
    update: { preference },
  });
}

/**
 * Löscht den eigenen Override, sodass die UI wieder auf den Kunden-
 * Default zurückfällt. Kein 404, wenn kein Eintrag da war – das ist
 * idempotent gedacht ("stelle sicher, dass kein Override existiert").
 */
export async function clearSalutationPreference(userId: number, customerId: number) {
  await assertCustomerExists(customerId);
  await prisma.userCustomerSalutation.deleteMany({
    where: { userId, customerId },
  });
}
