"use server";

import type mongooseType from "mongoose";
import { revalidatePath } from "next/cache";
import { hasPermission } from "@/lib/adminAuth";
import { getSessionClientId } from "@/lib/auth";
import { connectDB } from "@/lib/db";
import { DiscountCode } from "@/models/DiscountCode";
import { Order, type OrderDoc } from "@/models/Order";
import { Client } from "@/models/Client";
import { GiftVoucher } from "@/models/GiftVoucher";
import { Invoice } from "@/models/Invoice";
import { getPriceState } from "@/lib/priceProposal";
import { computeDiscountAmount } from "@/lib/discounts";
import { logActivity } from "@/lib/orderActivity";
import { formatEUR } from "@/lib/orderConstants";
import { getSettings } from "@/lib/settings";
import { randomRewardCode } from "@/lib/rewardCode";

const DISCOUNTS_ADMIN_PATH = "/de/admin/discounts";

type FormState = { error?: string };

function parseDate(value: FormDataEntryValue | null): Date | null {
  const s = String(value || "").trim();
  if (!s) return null;
  const d = new Date(s);
  return Number.isNaN(d.getTime()) ? null : d;
}

export async function createDiscountCodeAction(_prev: FormState, formData: FormData): Promise<FormState> {
  if (!(await hasPermission("discounts_create"))) return { error: "Keine Berechtigung." };

  const code = String(formData.get("code") || "").trim().toUpperCase();
  const type = formData.get("type") === "fixed" ? "fixed" : "percent";
  const value = Number(formData.get("value"));
  const maxUsesRaw = String(formData.get("maxUses") || "").trim();
  const maxUses = maxUsesRaw ? Math.max(1, Math.round(Number(maxUsesRaw))) : null;

  if (!code) return { error: "Bitte einen Code eingeben." };
  if (!value || value <= 0) return { error: "Bitte einen gültigen Wert eingeben." };
  if (type === "percent" && value > 100) return { error: "Prozent-Rabatt darf maximal 100 sein." };

  await connectDB();
  const existing = await DiscountCode.findOne({ code });
  if (existing) return { error: "Dieser Code existiert bereits." };

  const conventionIdRaw = String(formData.get("conventionId") || "");
  const conventionId = /^[a-f0-9]{24}$/i.test(conventionIdRaw) ? conventionIdRaw : null;

  await DiscountCode.create({
    code,
    type,
    value,
    maxUses,
    validUntil: parseDate(formData.get("validUntil")),
    stammkundenOnly: formData.get("stammkundenOnly") === "on",
    neukundenOnly: formData.get("neukundenOnly") === "on",
    conventionId,
    active: true,
    note: String(formData.get("note") || "").trim(),
  });

  revalidatePath(DISCOUNTS_ADMIN_PATH);
  return {};
}

export async function updateDiscountCodeAction(codeId: string, formData: FormData) {
  if (!(await hasPermission("discounts_edit"))) return;

  const type = formData.get("type") === "fixed" ? "fixed" : "percent";
  const value = Number(formData.get("value"));
  if (!value || value <= 0) return;
  if (type === "percent" && value > 100) return;

  const maxUsesRaw = String(formData.get("maxUses") || "").trim();
  const maxUses = maxUsesRaw ? Math.max(1, Math.round(Number(maxUsesRaw))) : null;

  const conventionIdRaw = String(formData.get("conventionId") || "");
  const conventionId = /^[a-f0-9]{24}$/i.test(conventionIdRaw) ? conventionIdRaw : null;

  await connectDB();
  await DiscountCode.findByIdAndUpdate(codeId, {
    type,
    value,
    maxUses,
    validUntil: parseDate(formData.get("validUntil")),
    stammkundenOnly: formData.get("stammkundenOnly") === "on",
    neukundenOnly: formData.get("neukundenOnly") === "on",
    conventionId,
    note: String(formData.get("note") || "").trim(),
  });

  revalidatePath(DISCOUNTS_ADMIN_PATH);
}

export async function setDiscountCodeActiveAction(codeId: string, active: boolean) {
  if (!(await hasPermission("discounts_edit"))) return;
  await connectDB();
  await DiscountCode.findByIdAndUpdate(codeId, { active });
  revalidatePath(DISCOUNTS_ADMIN_PATH);
}

export async function deleteDiscountCodeAction(codeId: string) {
  if (!(await hasPermission("discounts_delete"))) return;
  await connectDB();
  await DiscountCode.findByIdAndDelete(codeId);
  revalidatePath(DISCOUNTS_ADMIN_PATH);
}

type RedeemResult = { error?: string; success?: boolean; discountAmount?: number };

export async function redeemDiscountCodeAction(orderId: string, rawCode: string): Promise<RedeemResult> {
  const clientId = await getSessionClientId();
  if (!clientId) return { error: "Bitte logge dich ein." };

  const code = rawCode.trim().toUpperCase();
  if (!code) return { error: "Bitte einen Code eingeben." };

  await connectDB();
  const order = await Order.findById(orderId);
  if (!order || String(order.clientId) !== clientId) return { error: "Auftrag nicht gefunden." };
  if (order.discountCode) return { error: "Es wurde bereits ein Rabattcode für diesen Auftrag eingelöst." };
  // Rechnungen sind unveränderlich (siehe createInvoiceAction) — ein danach
  // noch eingelöster Rabatt würde den fälligen Betrag (z. B. bei der
  // Stripe-Direktzahlung) von der bereits ausgestellten Rechnung abweichen
  // lassen, ohne dass das irgendwo korrigiert würde.
  if (await Invoice.exists({ orderId })) {
    return { error: "Für diesen Auftrag wurde bereits eine Rechnung erstellt — ein Rabatt kann nicht mehr eingelöst werden." };
  }

  const { state } = await getPriceState(orderId);
  if (state.phase === "none") return { error: "Es liegt noch kein Preisvorschlag vor." };

  const discount = await DiscountCode.findOne({ code });

  // Kein regulärer Rabattcode mit diesem Code — erst prüfen, ob es sich um
  // den persönlichen Empfehlungscode eines anderen Kunden handelt, dann ob
  // es ein Geschenkgutschein ist.
  if (!discount) {
    const referrer = await Client.findOne({ referralCode: code });
    if (referrer) return redeemReferralCode(orderId, clientId, referrer, state.latest.amount, code);

    const voucher = await GiftVoucher.findOne({ code });
    if (voucher) return redeemGiftVoucher(orderId, order, voucher, state.latest.amount, code);

    return { error: "Dieser Rabattcode ist ungültig." };
  }

  if (discount.ownerId && String(discount.ownerId) !== clientId) {
    return { error: "Dieser Rabattcode ist einem anderen Konto zugeordnet." };
  }
  if (!discount.active) return { error: "Dieser Rabattcode ist ungültig." };
  if (discount.validUntil && discount.validUntil.getTime() < Date.now()) {
    return { error: "Dieser Rabattcode ist abgelaufen." };
  }
  if (discount.maxUses !== null && discount.usedCount >= discount.maxUses) {
    return { error: "Dieser Rabattcode wurde bereits zu oft verwendet." };
  }
  if (discount.stammkundenOnly || discount.neukundenOnly) {
    const totalOrders = await Order.countDocuments({ clientId });
    if (discount.stammkundenOnly && totalOrders <= 1) {
      return { error: "Dieser Rabattcode ist nur für Stammkunden gültig." };
    }
    if (discount.neukundenOnly && totalOrders > 1) {
      return { error: "Dieser Rabattcode ist nur für Neukunden gültig." };
    }
  }
  if (discount.conventionId && String(order.conventionId || "") !== String(discount.conventionId)) {
    return { error: "Dieser Rabattcode gilt nur für Aufträge zu einer bestimmten Convention." };
  }

  const discountAmount = computeDiscountAmount(discount.type, discount.value, state.latest.amount);

  // Atomarer Claim direkt vor dem Speichern (statt der reinen Lese-Prüfung
  // oben, die nur den Normalfall schnell und freundlich abfängt) — zwei
  // gleichzeitige Einlösungen desselben, auf 1 Nutzung begrenzten Codes
  // könnten sonst beide die Lese-Prüfung passieren, bevor eine von beiden
  // erhöht. Schlägt der Claim fehl, wurde der Code zwischenzeitlich
  // anderweitig aufgebraucht.
  if (discount.maxUses !== null) {
    const claimed = await DiscountCode.findOneAndUpdate(
      { _id: discount._id, $expr: { $lt: ["$usedCount", "$maxUses"] } },
      { $inc: { usedCount: 1 } }
    );
    if (!claimed) return { error: "Dieser Rabattcode wurde bereits zu oft verwendet." };
  } else {
    await DiscountCode.findByIdAndUpdate(discount._id, { $inc: { usedCount: 1 } });
  }

  // Atomarer Claim auch auf der Auftrags-Seite (statt der reinen Lese-
  // Prüfung ganz oben) — zwei gleichzeitige Einlösungen zweier
  // verschiedener Codes für denselben Auftrag könnten sonst beide den
  // jeweiligen Code erfolgreich verbrauchen und erst beim Speichern auf
  // dem Auftrag kollidieren (letzter Schreibzugriff gewinnt, der andere
  // Code wäre grundlos verbrannt). Schlägt dieser Schritt fehl, wird der
  // gerade verbrauchte Code wieder freigegeben.
  const updatedOrder = await Order.findOneAndUpdate(
    { _id: orderId, discountCode: "" },
    { $set: { discountCode: code, discountAmount } }
  );
  if (!updatedOrder) {
    await DiscountCode.findByIdAndUpdate(discount._id, { $inc: { usedCount: -1 } });
    return { error: "Für diesen Auftrag wurde inzwischen bereits ein anderer Rabattcode eingelöst." };
  }

  revalidatePath(`/de/account/orders/${orderId}`);
  revalidatePath(`/en/account/orders/${orderId}`);
  revalidatePath(`/de/admin/orders/${orderId}`);
  return { success: true, discountAmount };
}

// Empfehlungscodes sind personengebunden (Client.referralCode), nicht Teil
// der DiscountCode-Sammlung — nur für echte Neukunden einlösbar (dieser
// Auftrag ist ihr einziger), nicht für den eigenen Code. Bei erfolgreicher
// Einlösung bekommt der Werber automatisch einen neuen, auf ihn beschränkten
// Rabattcode (DiscountCode.ownerId) gutgeschrieben.
async function redeemReferralCode(
  orderId: string,
  clientId: string,
  referrer: { _id: string; name: string },
  latestAmount: number,
  code: string
): Promise<RedeemResult> {
  if (String(referrer._id) === clientId) {
    return { error: "Du kannst deinen eigenen Empfehlungscode nicht einlösen." };
  }

  const settings = await getSettings();
  if (!settings.referralSettings?.enabled) return { error: "Das Empfehlungsprogramm ist derzeit nicht aktiv." };

  const totalOrders = await Order.countDocuments({ clientId });
  if (totalOrders > 1) return { error: "Empfehlungscodes sind nur für Neukunden gültig." };

  const referee = await Client.findById(clientId).select("name").lean();
  const { refereeDiscountType, refereeDiscountValue, referrerRewardType, referrerRewardValue } = settings.referralSettings;

  const discountAmount = computeDiscountAmount(refereeDiscountType, refereeDiscountValue, latestAmount);

  const updatedOrder = await Order.findOneAndUpdate(
    { _id: orderId, discountCode: "" },
    { $set: { discountCode: code, discountAmount } }
  );
  if (!updatedOrder) {
    return { error: "Für diesen Auftrag wurde inzwischen bereits ein anderer Rabattcode eingelöst." };
  }

  // Der Rabatt fürs neue Konto ist oben bereits fest eingelöst — ein Fehler
  // bei der Empfehlungsprämie (z. B. seltene Code-Kollision) darf diesen
  // Erfolg nicht rückwirkend als Fehler ausgeben, daher separat abgesichert.
  try {
    let rewardCode = randomRewardCode();
    while (await DiscountCode.findOne({ code: rewardCode })) rewardCode = randomRewardCode();

    await DiscountCode.create({
      code: rewardCode,
      type: referrerRewardType,
      value: referrerRewardValue,
      maxUses: 1,
      ownerId: referrer._id,
      active: true,
      note: `Empfehlungsprämie — ${referee?.name || "ein Neukunde"} wurde erfolgreich geworben.`,
    });
  } catch (err) {
    console.error("Empfehlungsprämie fehlgeschlagen:", err);
  }

  revalidatePath(`/de/account/orders/${orderId}`);
  revalidatePath(`/en/account/orders/${orderId}`);
  revalidatePath(`/de/admin/orders/${orderId}`);
  return { success: true, discountAmount };
}

// Geschenkgutschein einlösen — wirkt wie ein Rabattcode mit festem Betrag,
// aber aus einer eigenen Sammlung (echtes, vorab bezahltes Geld statt eines
// Marketing-Rabatts). Restguthaben über dem Auftragswert verfällt bewusst
// (kein Auszahlungsanspruch auf die Differenz) — Standardverhalten bei
// Gutscheinen, spiegelt auch computeDiscountAmount()'s Deckelung.
async function redeemGiftVoucher(
  orderId: string,
  order: mongooseType.HydratedDocument<OrderDoc>,
  voucher: mongooseType.HydratedDocument<import("@/models/GiftVoucher").GiftVoucherDoc>,
  latestAmount: number,
  code: string
): Promise<RedeemResult> {
  if (voucher.cancelled) return { error: "Dieser Gutschein ist ungültig." };
  if (!voucher.paid) return { error: "Dieser Gutschein wurde noch nicht bezahlt." };
  if (voucher.redeemed) return { error: "Dieser Gutschein wurde bereits eingelöst." };
  if (voucher.expiresAt && voucher.expiresAt.getTime() < Date.now()) {
    return { error: "Dieser Gutschein ist abgelaufen." };
  }

  const discountAmount = computeDiscountAmount("fixed", voucher.amount, latestAmount);

  // Atomarer Claim direkt vor dem Speichern, gleiches Prinzip wie bei
  // Rabattcodes oben — verhindert, dass derselbe Gutschein durch zwei
  // gleichzeitige Einlöse-Versuche doppelt vergeben wird (die vorherige
  // Lese-Prüfung "if (voucher.redeemed)" allein ist nicht rennsicher).
  const claimed = await GiftVoucher.findOneAndUpdate(
    { _id: voucher._id, redeemed: false },
    {
      $set: {
        redeemed: true,
        redeemedByClientId: order.clientId,
        redeemedOrderId: order._id as unknown as mongooseType.Types.ObjectId,
        redeemedAt: new Date(),
      },
    }
  );
  if (!claimed) return { error: "Dieser Gutschein wurde bereits eingelöst." };

  // Wie beim regulären Rabattcode: erst nach dem atomaren Gutschein-Claim
  // auch die Auftrags-Seite atomar schreiben, und den Claim zurückrollen,
  // falls der Auftrag zwischenzeitlich (Race) schon einen anderen Code
  // bekommen hat — sonst wäre das eingelöste Guthaben ersatzlos verloren.
  const updatedOrder = await Order.findOneAndUpdate(
    { _id: orderId, discountCode: "" },
    { $set: { discountCode: code, discountAmount } }
  );
  if (!updatedOrder) {
    await GiftVoucher.findByIdAndUpdate(voucher._id, {
      $set: { redeemed: false },
      $unset: { redeemedByClientId: "", redeemedOrderId: "", redeemedAt: "" },
    });
    return { error: "Für diesen Auftrag wurde inzwischen bereits ein anderer Rabattcode eingelöst." };
  }

  revalidatePath(`/de/account/orders/${orderId}`);
  revalidatePath(`/en/account/orders/${orderId}`);
  revalidatePath(`/de/admin/orders/${orderId}`);
  return { success: true, discountAmount };
}

export async function removeOrderDiscountAction(orderId: string): Promise<FormState> {
  if (!(await hasPermission("discounts_edit"))) return {};
  await connectDB();
  const order = await Order.findById(orderId);
  if (!order || !order.discountCode) return {};
  // Siehe redeemDiscountCodeAction: nach Rechnungsstellung darf sich der
  // fällige Betrag nicht mehr ändern, sonst weicht er von der bereits
  // ausgestellten (unveränderlichen) Rechnung ab.
  if (await Invoice.exists({ orderId })) {
    return { error: "Für diesen Auftrag wurde bereits eine Rechnung erstellt — der Rabatt kann nicht mehr entfernt werden." };
  }

  const removedDiscountCode = await DiscountCode.findOneAndUpdate({ code: order.discountCode }, { $inc: { usedCount: -1 } });
  if (!removedDiscountCode) {
    // Kein regulärer Rabattcode mit diesem Code — evtl. ein für genau
    // diesen Auftrag eingelöster Geschenkgutschein, dessen "eingelöst"-
    // Status sonst dauerhaft hängen bliebe und das Guthaben verloren wäre.
    await GiftVoucher.findOneAndUpdate(
      { code: order.discountCode, redeemedOrderId: order._id },
      { $set: { redeemed: false }, $unset: { redeemedByClientId: "", redeemedOrderId: "", redeemedAt: "" } }
    );
  }
  order.discountCode = "";
  order.discountAmount = 0;
  await order.save();

  revalidatePath(`/de/admin/orders/${orderId}`);
  return {};
}

// Rabatt, den das Team direkt an einem Auftrag gewährt (z. B. Kulanz) —
// unabhängig davon, ob ein Rabattcode eingelöst wurde. Bewusst über die
// pricing-Berechtigung statt discounts, weil es eine Preisentscheidung am
// einzelnen Auftrag ist, keine Verwaltung des Codesystems.
export async function setManualDiscountAction(orderId: string, formData: FormData): Promise<FormState> {
  if (!(await hasPermission("pricing"))) return {};

  const amount = Number(formData.get("amount"));
  if (!Number.isFinite(amount) || amount <= 0) return { error: "Bitte einen gültigen Betrag angeben." };
  const note = String(formData.get("note") || "").trim();

  await connectDB();
  const order = await Order.findById(orderId);
  if (!order) return {};
  // Siehe redeemDiscountCodeAction: nach Rechnungsstellung darf sich der
  // fällige Betrag nicht mehr ändern, sonst weicht er von der bereits
  // ausgestellten (unveränderlichen) Rechnung ab.
  if (await Invoice.exists({ orderId })) {
    return { error: "Für diesen Auftrag wurde bereits eine Rechnung erstellt — ein Rabatt kann nicht mehr gewährt werden." };
  }

  order.manualDiscountAmount = Math.round(amount * 100) / 100;
  order.manualDiscountNote = note;
  await order.save();

  await logActivity(
    orderId,
    "manual_discount_granted",
    "admin",
    `Manueller Rabatt gewährt: ${formatEUR(order.manualDiscountAmount)}${note ? ` — ${note}` : ""}`
  );

  revalidatePath(`/de/admin/orders/${orderId}`);
  revalidatePath(`/de/account/orders/${orderId}`);
  revalidatePath(`/en/account/orders/${orderId}`);
  return {};
}

export async function removeManualDiscountAction(orderId: string): Promise<FormState> {
  if (!(await hasPermission("pricing"))) return {};
  await connectDB();
  const order = await Order.findById(orderId);
  if (!order || !order.manualDiscountAmount) return {};
  if (await Invoice.exists({ orderId })) {
    return { error: "Für diesen Auftrag wurde bereits eine Rechnung erstellt — der Rabatt kann nicht mehr entfernt werden." };
  }

  order.manualDiscountAmount = 0;
  order.manualDiscountNote = "";
  await order.save();

  await logActivity(orderId, "manual_discount_removed", "admin", "Manueller Rabatt entfernt");

  revalidatePath(`/de/admin/orders/${orderId}`);
  revalidatePath(`/de/account/orders/${orderId}`);
  revalidatePath(`/en/account/orders/${orderId}`);
  return {};
}
