"use server";

import crypto from "crypto";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
import { connectDB } from "@/lib/db";
import { Order, SHOOTING_TYPES, type ShootingType } from "@/models/Order";
import { Service } from "@/models/Service";
import { Bundle } from "@/models/Bundle";
import { PriceProposal } from "@/models/PriceProposal";
import { Contract } from "@/models/Contract";
import { ClientPhotoFolder } from "@/models/ClientPhotoFolder";
import { PhotoRating } from "@/models/PhotoRating";
import { OrderMessage } from "@/models/OrderMessage";
import { getSessionClientId } from "@/lib/auth";
import { saveOrderAttachments, MAX_FILES } from "@/lib/uploads";
import { getPriceState } from "@/lib/priceProposal";
import { logActivity } from "@/lib/orderActivity";
import { formatEUR, DEFAULT_CHECKLIST_ITEMS } from "@/lib/orderConstants";
import { headers } from "next/headers";
import { notifyStaffWithPermission, notifyRecipient } from "@/lib/notifications";

async function assertOrderOwnership(orderId: string, clientId: string) {
  const order = await Order.findById(orderId).lean();
  return order && String(order.clientId) === clientId ? order : null;
}

// Kunde nimmt den Vorschlag von Rakku an oder lehnt ihn ab — niemals selbst
// den ersten Vorschlag machen, nur reagieren.
export async function clientRespondPriceAction(orderId: string, decision: "accepted" | "rejected") {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();
  if (!(await assertOrderOwnership(orderId, clientId))) return;

  const { state } = await getPriceState(orderId);
  if (state.phase !== "waiting_on_client") return;

  await PriceProposal.findByIdAndUpdate(state.latest._id, { status: decision });
  await logActivity(
    orderId,
    decision === "accepted" ? "price_accepted" : "price_rejected",
    "client",
    decision === "accepted" ? "Preis angenommen" : "Preisvorschlag abgelehnt"
  );
  revalidatePath("/[locale]/account", "page");
}

export async function clientCounterPriceAction(orderId: string, amount: number, note: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();
  if (!(await assertOrderOwnership(orderId, clientId))) return;

  const { state } = await getPriceState(orderId);
  if (state.phase !== "waiting_on_client") return;

  await PriceProposal.findByIdAndUpdate(state.latest._id, { status: "countered" });
  await PriceProposal.create({ orderId, proposedBy: "client", amount, note, status: "pending" });
  await logActivity(orderId, "price_countered", "client", `Gegenvorschlag: ${formatEUR(amount)}${note ? ` — ${note}` : ""}`);
  revalidatePath("/[locale]/account", "page");
}

// Kunde storniert sein eigenes Shooting mit Begründung — unabhängig vom
// admin-seitigen Ablehnungs-Workflow (eigene Gründe, kein Zugriff auf die
// admin-verwaltete Gründe-Liste).
export async function clientCancelOrderAction(orderId: string, reason: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();
  const order = await assertOrderOwnership(orderId, clientId);
  if (!order || order.rejected || order.tag.startsWith("done_")) return;

  const trimmedReason = reason.trim();
  if (!trimmedReason) return;

  await Order.findByIdAndUpdate(orderId, {
    rejected: true,
    rejectionReason: trimmedReason,
    rejectionNote: "",
    rejectedAt: new Date(),
    rejectedBy: "client",
    tag: "done_dropped",
    doneAt: new Date(),
  });
  await logActivity(orderId, "order_rejected", "client", `Auftrag vom Kunden storniert: ${trimmedReason}`);
  revalidatePath("/[locale]/account", "page");
  revalidatePath("/[locale]/account/orders/[orderId]", "page");
}

export async function clientSendOrderMessageAction(orderId: string, text: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();
  const order = await assertOrderOwnership(orderId, clientId);
  if (!order) return;

  const trimmed = text.trim();
  if (!trimmed) return;

  await OrderMessage.create({ orderId, senderId: clientId, senderType: "client", text: trimmed });

  const notifyData = {
    type: "new_message_for_staff" as const,
    title: `Neue Nachricht — ${order.characterOrConcept}`,
    body: trimmed.slice(0, 200),
    link: `/de/admin/orders/${orderId}`,
  };
  if (order.assignedStaffId) {
    await notifyRecipient(String(order.assignedStaffId), notifyData);
  } else {
    await notifyStaffWithPermission("orders_view", notifyData);
  }

  revalidatePath("/[locale]/account/orders/[orderId]", "page");
  revalidatePath("/de/admin/orders");
}

type FormState = { error?: string };

export async function createOrderAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const locale = String(formData.get("locale") || "de");
  const clientId = await getSessionClientId();
  if (!clientId) redirect(`/${locale}/login`);

  const shootingType = String(formData.get("shootingType") || "");
  const characterOrConcept = String(formData.get("characterOrConcept") || "").trim();
  const eventLocation = String(formData.get("eventLocation") || "").trim();
  const datePeriod = String(formData.get("datePeriod") || "").trim();
  const earlyDateRequested = formData.get("earlyDateRequested") === "on";
  const earlyDateReason = earlyDateRequested ? String(formData.get("earlyDateReason") || "").trim() : "";
  const instagramHandle = String(formData.get("instagramHandle") || "").trim();
  const discordHandle = String(formData.get("discordHandle") || "").trim();
  const ideaMood = String(formData.get("ideaMood") || "").trim();
  const additionalNotes = String(formData.get("additionalNotes") || "").trim();

  const isEn = locale === "en";
  if (!SHOOTING_TYPES.includes(shootingType as ShootingType)) {
    return { error: isEn ? "Please choose a shooting type." : "Bitte wähle eine Art des Shootings." };
  }
  if (!characterOrConcept) {
    return { error: isEn ? "Please describe your idea." : "Bitte beschreibe kurz deine Idee." };
  }

  const files = formData.getAll("attachments").filter((f): f is File => f instanceof File && f.size > 0);
  if (files.length > MAX_FILES) {
    return { error: isEn ? `Maximum ${MAX_FILES} files.` : `Maximal ${MAX_FILES} Dateien.` };
  }

  await connectDB();

  // Preisstufe serverseitig nachschlagen statt dem Client zu vertrauen — nur
  // Titel/Name/Preis werden als Schnappschuss übernommen, keine über die
  // URL manipulierbaren Werte. Rein informativ für den Admin, die
  // eigentliche Preisverhandlung läuft weiterhin über PriceProposal.
  const serviceId = String(formData.get("serviceId") || "");
  const tierId = String(formData.get("tierId") || "");
  let bookedServiceTitle = "";
  let bookedTierName = "";
  let bookedTierPrice = 0;
  if (serviceId && tierId) {
    const service = await Service.findById(serviceId).lean();
    const tier = service?.tiers.find((t) => String(t._id) === tierId);
    if (service && tier) {
      bookedServiceTitle = (isEn && service.titleEn) || service.title;
      bookedTierName = (isEn && tier.nameEn) || tier.name;
      bookedTierPrice = tier.price;
    }
  }

  const bundleId = String(formData.get("bundleId") || "");
  let bookedBundleTitle = "";
  let bookedBundlePrice = 0;
  if (bundleId) {
    const bundle = await Bundle.findById(bundleId).lean();
    if (bundle && bundle.active) {
      bookedBundleTitle = (isEn && bundle.titleEn) || bundle.title;
      bookedBundlePrice = bundle.price;
    }
  }

  const order = await Order.create({
    clientId,
    shootingType: shootingType as ShootingType,
    characterOrConcept,
    eventLocation,
    datePeriod,
    earlyDateRequested,
    earlyDateReason,
    instagramHandle,
    discordHandle,
    ideaMood,
    additionalNotes,
    bookedServiceTitle,
    bookedTierName,
    bookedTierPrice,
    bookedBundleTitle,
    bookedBundlePrice,
    checklist: DEFAULT_CHECKLIST_ITEMS.map((text) => ({ text, done: false, doneAt: null })),
  });

  if (files.length) {
    try {
      const attachments = await saveOrderAttachments(String(order._id), files);
      order.attachments = attachments;
      await order.save();
    } catch (err) {
      return { error: err instanceof Error ? err.message : "Upload fehlgeschlagen." };
    }
  }

  await logActivity(String(order._id), "order_created", "client", "Auftrag erstellt");
  await notifyStaffWithPermission("orders_view", {
    type: "new_order",
    title: "Neue Auftragsanfrage",
    body: characterOrConcept,
    link: `/de/admin/orders/${order._id}`,
  });
  redirect(`/${locale}/account`);
}

// Erlaubt dem Kunden, nach der Anfrage noch weitere Referenzbilder/Moodboard-
// Ideen nachzureichen (z. B. wenn die Pose-Idee erst später konkret wird) —
// bisher ging das nur einmalig bei der Anfrage selbst. Bewusst nicht bei
// bereits abgelehnten/archivierten Aufträgen möglich, da dort kein Shooting
// mehr bevorsteht, für das eine Referenz noch relevant wäre.
export async function addOrderAttachmentsAction(orderId: string, _prev: FormState, formData: FormData): Promise<FormState> {
  const clientId = await getSessionClientId();
  if (!clientId) return { error: "Bitte logge dich ein." };

  await connectDB();
  const order = await Order.findById(orderId);
  if (!order || String(order.clientId) !== clientId) return { error: "Auftrag nicht gefunden." };
  if (order.rejected || order.archived) return { error: "Dieser Auftrag ist abgeschlossen." };

  const files = formData.getAll("attachments").filter((f): f is File => f instanceof File && f.size > 0);
  if (files.length === 0) return { error: "Bitte mindestens eine Datei auswählen." };
  if (order.attachments.length + files.length > MAX_FILES) {
    return { error: `Maximal ${MAX_FILES} Dateien insgesamt (${order.attachments.length} bereits vorhanden).` };
  }

  try {
    const newAttachments = await saveOrderAttachments(orderId, files);
    order.attachments.push(...newAttachments);
    await order.save();
  } catch (err) {
    return { error: err instanceof Error ? err.message : "Upload fehlgeschlagen." };
  }

  await logActivity(
    orderId,
    "attachments_added",
    "client",
    `${files.length} weitere${files.length === 1 ? "s Referenzbild" : " Referenzbilder"} hochgeladen`
  );
  revalidatePath("/[locale]/account/orders/[orderId]", "page");
  return {};
}

type SignFormState = { error?: string; success?: boolean };

export async function clientSignContractAction(_prev: SignFormState, formData: FormData): Promise<SignFormState> {
  const contractId = String(formData.get("contractId") || "");
  const locale = String(formData.get("locale") || "de");
  const modelName = String(formData.get("modelName") || "").trim();
  const guardian1Name = String(formData.get("guardian1Name") || "").trim();
  const guardian2Name = String(formData.get("guardian2Name") || "").trim();
  const consent = formData.get("consent") === "on";
  const isEn = locale === "en";

  const clientId = await getSessionClientId();
  if (!clientId) return { error: isEn ? "Please log in." : "Bitte logge dich ein." };

  await connectDB();
  const contract = await Contract.findById(contractId);
  if (!contract || String(contract.clientId) !== clientId) {
    return { error: isEn ? "Contract not found." : "Vertrag nicht gefunden." };
  }
  if (contract.status !== "sent") {
    return { error: isEn ? "This contract can no longer be signed." : "Dieser Vertrag kann nicht mehr unterschrieben werden." };
  }
  if (!modelName || !consent) {
    return {
      error: isEn
        ? "Please enter your name and confirm your consent."
        : "Bitte gib deinen Namen ein und bestätige die Zustimmung.",
    };
  }

  const isMinor = contract.type === "tfp" && contract.tfpData?.isMinor;
  const guardianCount = contract.type === "tfp" ? contract.tfpData?.guardians?.length ?? 0 : 0;
  if (isMinor && !guardian1Name) {
    return {
      error: isEn
        ? "A legal guardian's signature is required."
        : "Die Unterschrift einer erziehungsberechtigten Person wird benötigt.",
    };
  }
  if (isMinor && guardianCount > 1 && !guardian2Name) {
    return {
      error: isEn
        ? "Both legal guardians need to sign."
        : "Beide erziehungsberechtigten Personen müssen unterschreiben.",
    };
  }

  const ip = (await headers()).get("x-real-ip") || (await headers()).get("x-forwarded-for") || "unknown";
  const now = new Date();

  contract.signatures.push({ role: "model", name: modelName, signedAt: now, ip });
  if (isMinor && guardian1Name) contract.signatures.push({ role: "guardian1", name: guardian1Name, signedAt: now, ip });
  if (isMinor && guardian2Name) contract.signatures.push({ role: "guardian2", name: guardian2Name, signedAt: now, ip });

  contract.status = "signed";
  contract.signedAt = now;
  await contract.save();

  await logActivity(String(contract.orderId), "contract_signed", "client", "Vertrag vom Kunden unterschrieben");

  return { success: true };
}

export async function ratePhotoAction(folderId: string, fileName: string, stars: number) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();

  const folder = await ClientPhotoFolder.findById(folderId).lean();
  if (!folder || String(folder.clientId) !== clientId) return;

  await PhotoRating.findOneAndUpdate(
    { folderId, fileName },
    { $set: { stars }, $setOnInsert: { clientId, folderId, fileName } },
    { upsert: true }
  );
  revalidatePath(`/[locale]/account/photos/${folderId}`, "page");
}

export async function setPhotoNoteAction(folderId: string, fileName: string, note: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();

  const folder = await ClientPhotoFolder.findById(folderId).lean();
  if (!folder || String(folder.clientId) !== clientId) return;

  await PhotoRating.findOneAndUpdate(
    { folderId, fileName },
    { $set: { note: note.trim().slice(0, 500) }, $setOnInsert: { clientId, folderId, fileName } },
    { upsert: true }
  );
  revalidatePath(`/[locale]/account/photos/${folderId}`, "page");
  revalidatePath(`/de/admin/folders/${folderId}`);
}

export async function setPhotoCollectionAction(folderId: string, fileName: string, collection: string) {
  const clientId = await getSessionClientId();
  if (!clientId) return;
  await connectDB();

  const folder = await ClientPhotoFolder.findById(folderId).lean();
  if (!folder || String(folder.clientId) !== clientId) return;

  await PhotoRating.findOneAndUpdate(
    { folderId, fileName },
    { $set: { collection: collection.trim().slice(0, 80) }, $setOnInsert: { clientId, folderId, fileName } },
    { upsert: true }
  );
  revalidatePath(`/[locale]/account/photos/${folderId}`, "page");
}

// Teilbarer Lese-Link für Familie/Freunde ohne eigenes Konto (siehe
// api/photos/shared/[token]/[filename] und [locale]/galerie/[token]) — ein
// zufälliger Token statt der Ordner-ID selbst im Link, damit sich Ordner
// nicht einfach durchraten lassen. Toggle statt getrennter Aktivieren-/
// Deaktivieren-Aktionen, da die UI nur einen Schalter braucht.
const SHARE_LINK_VALID_DAYS = 30;

export async function toggleFolderShareAction(
  folderId: string
): Promise<{ shareToken: string | null; shareTokenExpiresAt?: string | null; error?: true }> {
  const clientId = await getSessionClientId();
  if (!clientId) return { shareToken: null, error: true };
  await connectDB();

  const folder = await ClientPhotoFolder.findById(folderId);
  if (!folder || String(folder.clientId) !== clientId) return { shareToken: null, error: true };

  if (folder.shareToken) {
    // undefined statt null — sonst würde der sparse-Index das Feld trotzdem
    // indexieren und der nächste Ordner ohne aktiven Teilen-Link würde mit
    // einem E11000-Duplicate-Key-Fehler auf shareToken kollidieren.
    folder.shareToken = undefined;
    folder.shareTokenExpiresAt = null;
  } else {
    folder.shareToken = crypto.randomBytes(16).toString("hex");
    folder.shareTokenExpiresAt = new Date(Date.now() + SHARE_LINK_VALID_DAYS * 24 * 60 * 60 * 1000);
  }
  await folder.save();

  revalidatePath(`/[locale]/account/photos/${folderId}`, "page");
  return {
    shareToken: folder.shareToken ?? null,
    shareTokenExpiresAt: folder.shareTokenExpiresAt ? folder.shareTokenExpiresAt.toISOString() : null,
  };
}

// Verlängert einen aktiven Teilen-Link um weitere 30 Tage ab jetzt, ohne den
// Token selbst zu ändern (bereits verschickte Links bleiben gültig).
export async function extendFolderShareAction(folderId: string): Promise<{ shareTokenExpiresAt: string | null; error?: true }> {
  const clientId = await getSessionClientId();
  if (!clientId) return { shareTokenExpiresAt: null, error: true };
  await connectDB();

  const folder = await ClientPhotoFolder.findById(folderId);
  if (!folder || String(folder.clientId) !== clientId || !folder.shareToken) return { shareTokenExpiresAt: null, error: true };

  folder.shareTokenExpiresAt = new Date(Date.now() + SHARE_LINK_VALID_DAYS * 24 * 60 * 60 * 1000);
  await folder.save();

  revalidatePath(`/[locale]/account/photos/${folderId}`, "page");
  return { shareTokenExpiresAt: folder.shareTokenExpiresAt.toISOString() };
}
