import crypto from "crypto";
import path from "path";
import { unlink, rm } from "fs/promises";
import { connectDB } from "@/lib/db";
import { hashPassword } from "@/lib/auth";
import { avatarUploadPath, ORDER_UPLOAD_ROOT } from "@/lib/uploads";
import { Client } from "@/models/Client";
import { Order } from "@/models/Order";
import { Contract } from "@/models/Contract";
import { Reminder } from "@/models/Reminder";
import { OrderFeedback } from "@/models/OrderFeedback";
import { PhotoRating } from "@/models/PhotoRating";
import { ClientPhotoFolder } from "@/models/ClientPhotoFolder";
import { GeneratedDocument } from "@/models/GeneratedDocument";
import { OrderMessage } from "@/models/OrderMessage";
import { DiscountCode } from "@/models/DiscountCode";
import { GiftVoucher } from "@/models/GiftVoucher";
import { Invoice } from "@/models/Invoice";
import { PriceProposal } from "@/models/PriceProposal";
import { OrderActivity } from "@/models/OrderActivity";
import { Notification } from "@/models/Notification";
import { Todo } from "@/models/Todo";
import { WaitlistEntry } from "@/models/WaitlistEntry";
import { WaitlistMessage } from "@/models/WaitlistMessage";
import { CharacterWish } from "@/models/CharacterWish";

// Rechnungen müssen in Deutschland 10 Jahre aufbewahrt werden (§ 147 AO) —
// ein Kundenkonto mit vorhandenen Rechnungen darf daher nie hart gelöscht
// werden, nur anonymisiert. Ohne Rechnung (z. B. reine Kontoerstellung ohne
// Auftrag, oder Auftrag ohne Rechnungsstellung) ist eine echte Löschung
// unbedenklich möglich.
export async function clientHasInvoices(clientId: string) {
  await connectDB();
  return (await Invoice.exists({ clientId })) !== null;
}

async function removeAvatarFile(clientId: string, avatarPath: string) {
  if (!avatarPath) return;
  try {
    await unlink(avatarUploadPath(clientId, avatarPath));
  } catch {
    // Datei war schon weg — nicht schlimm.
  }
}

export async function hardDeleteClient(clientId: string) {
  await connectDB();
  const client = await Client.findById(clientId);
  if (!client) return;

  const orders = await Order.find({ clientId }).select("_id discountCode").lean();
  const orderIds = orders.map((o) => o._id);
  const waitlistEntries = await WaitlistEntry.find({ clientId }).select("_id").lean();
  const waitlistEntryIds = waitlistEntries.map((e) => e._id);

  // Rabattcodes, die auf einen gleich gelöschten Auftrag eingelöst wurden,
  // müssen ihren usedCount zurückbekommen — sonst bleibt ein Code für immer
  // fälschlich "aufgebraucht", auch wenn einer seiner gezählten Einlösungen
  // rückwirkend verschwindet. Ein Code kann von mehrereren gelöschten
  // Aufträgen dieses Kunden genutzt worden sein, daher pro Code zählen.
  const usedCodeCounts = new Map<string, number>();
  for (const o of orders) {
    if (o.discountCode) usedCodeCounts.set(o.discountCode, (usedCodeCounts.get(o.discountCode) || 0) + 1);
  }

  await Promise.all([
    Order.deleteMany({ clientId }),
    Contract.deleteMany({ clientId }),
    Reminder.deleteMany({ clientId }),
    OrderFeedback.deleteMany({ clientId }),
    PhotoRating.deleteMany({ clientId }),
    ClientPhotoFolder.deleteMany({ clientId }),
    GeneratedDocument.deleteMany({ clientId }),
    OrderMessage.deleteMany({ orderId: { $in: orderIds } }),
    // Ohne diese beiden blieben verwaiste PriceProposal-/OrderActivity-
    // Dokumente zurück (kein Order mehr, auf den sie zeigen) — akzeptierte
    // Preisvorschläge ohne zugehörigen Auftrag würden sonst dauerhaft
    // fälschlich in den Umsatzstatistiken mitgezählt (siehe statistics/page.tsx).
    PriceProposal.deleteMany({ orderId: { $in: orderIds } }),
    OrderActivity.deleteMany({ orderId: { $in: orderIds } }),
    // Codes mit ownerId sind IMMER persönliche Prämien-Codes (Loyalität/
    // Bewertung/Empfehlung, siehe DiscountCode.ownerId-Kommentar im Modell) —
    // anders als bei admin-angelegten Marketing-Codes gibt es hier niemanden
    // sonst, der sie legitim einlösen dürfte. Ein bloßes Entfernen der
    // ownerId (wie früher) würde einen noch nicht eingelösten Code
    // versehentlich in einen für jeden einlösbaren Code verwandeln (ownerId
    // null = uneingeschränkt, siehe discounts.ts). Unbenutzte Codes daher
    // löschen; bereits benutzte bleiben unangetastet (ohnehin nicht mehr
    // einlösbar, dienen nur noch als Buchungs-Historie).
    DiscountCode.deleteMany({ ownerId: clientId, usedCount: 0 }),
    // Wie removeOrderDiscountAction (discounts.ts): komplette Einlösung
    // zurücksetzen, nicht nur die Kunden-Verknüpfung — sonst bleibt der
    // Gutschein für immer als "eingelöst" gegen einen nicht mehr
    // existierenden Auftrag gesperrt, das Guthaben wäre unwiderruflich weg.
    GiftVoucher.updateMany(
      { redeemedByClientId: clientId },
      { $set: { redeemed: false }, $unset: { redeemedByClientId: "", redeemedOrderId: "", redeemedAt: "" } }
    ),
    Notification.deleteMany({ recipientId: clientId }),
    // Nur die Verknüpfung lösen, nicht das ToDo selbst — der Eintrag kann
    // weiterhin für das Team relevant sein, nur der Auftragsbezug ist tot.
    Todo.updateMany({ orderId: { $in: orderIds } }, { $set: { orderId: null } }),
    WaitlistEntry.deleteMany({ clientId }),
    WaitlistMessage.deleteMany({ waitlistEntryId: { $in: waitlistEntryIds } }),
    CharacterWish.deleteMany({ clientId }),
    ...[...usedCodeCounts.entries()].map(([code, count]) =>
      DiscountCode.updateOne({ code }, { $inc: { usedCount: -count } })
    ),
  ]);

  // Referenzbild-Uploads liegen lokal unter private-uploads/orders/<orderId>/
  // (nicht in Nextcloud) — ohne diesen Schritt blieben sie nach dem Löschen
  // des Order-Dokuments für immer verwaist auf der Platte liegen, weil sich
  // ihr Pfad nirgends mehr nachschlagen ließe.
  await Promise.all(orderIds.map((id) => rm(path.join(ORDER_UPLOAD_ROOT, String(id)), { recursive: true, force: true })));

  await removeAvatarFile(clientId, client.avatarPath);
  await Client.deleteOne({ _id: clientId });
}

export async function anonymizeClient(clientId: string) {
  await connectDB();
  const client = await Client.findById(clientId);
  if (!client) return;

  await removeAvatarFile(clientId, client.avatarPath);

  client.name = "Gelöschter Kunde";
  client.firstName = "";
  client.lastName = "";
  client.nickname = "";
  client.company = "";
  client.email = `geloescht-${client._id}@rakku.invalid`;
  client.passwordHash = await hashPassword(crypto.randomBytes(32).toString("hex"));
  client.phone = "";
  client.address = "";
  client.billingStreet = "";
  client.billingPostCode = "";
  client.billingCity = "";
  client.avatarPath = "";
  client.birthDate = null;
  client.tags = [];
  client.referralCode = "";
  client.verified = false;
  client.totpEnabled = false;
  client.totpSecret = "";
  client.totpRecoveryCodeHashes = [];
  client.deletionRequestedAt = null;
  client.anonymizedAt = new Date();
  await client.save();

  // Name/Anschrift werden oben überschrieben, aber Freitext-Felder in neueren
  // Modellen (Warteliste, Wunschliste) können ebenfalls identifizierende
  // Angaben enthalten (z. B. eine im Chat unterschriebene Nachricht) und
  // unterliegen — anders als Rechnungen — keiner gesetzlichen Aufbewahrungs-
  // pflicht. Chatnachrichten anderer Teilnehmer:innen (Team) bleiben unangetastet.
  const waitlistEntryIds = (await WaitlistEntry.find({ clientId }).select("_id").lean()).map((e) => e._id);
  await Promise.all([
    WaitlistEntry.updateMany({ clientId }, { $set: { message: "" } }),
    WaitlistMessage.updateMany(
      { waitlistEntryId: { $in: waitlistEntryIds }, senderId: clientId },
      { $set: { text: "[Nachricht von gelöschtem Konto entfernt]" } }
    ),
    CharacterWish.deleteMany({ clientId }),
  ]);
}
