"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { prisma } from "@/lib/prisma";
import { logActivity } from "@/lib/activity";
import { notifyCustomer, notifyManagement } from "@/lib/notify";
import { customerBalance, unitPriceFor } from "@/lib/pricing";
import { actor, failure } from "./guard";

export async function nextOrderRef() {
  const count = await prisma.order.count();
  return `ORD-${1001 + count}`;
}

/** Product lines posted by the line-item editor, merged by product. */
export async function readLines(formData: FormData) {
  const productIds = formData.getAll("productId").map(String);
  const quantities = formData.getAll("quantity").map(Number);
  const merged = new Map<string, number>();
  productIds.forEach((productId, index) => {
    const quantity = Math.floor(quantities[index] ?? 0);
    if (productId && quantity > 0) merged.set(productId, (merged.get(productId) ?? 0) + quantity);
  });
  return [...merged.entries()].map(([productId, quantity]) => ({ productId, quantity }));
}

export async function createOrder(formData: FormData) {
  const user = await actor("orders", "create", formData);
  const customerId = String(formData.get("customerId") ?? "");
  const customer = await prisma.customer.findUnique({ where: { id: customerId } });
  if (!customer) throw await failure("Customer not found");
  const lines = [];
  let total = 0;
  for (const item of await readLines(formData)) {
    const unitPrice = await unitPriceFor(item.productId, customer.accountType, item.quantity);
    total += unitPrice * item.quantity;
    lines.push({ ...item, unitPrice });
  }
  if (!lines.length) throw await failure("Add at least one product.");
  const minimum = await prisma.accountMinimum.findUnique({ where: { accountType: customer.accountType } });
  if (minimum && total < minimum.minOrderAmount) {
    throw await failure("Minimum order for this account type is {amount}.", { amount: minimum.minOrderAmount });
  }
  const balance = await customerBalance(customerId);
  if (customer.creditLimit && balance.remaining + total > customer.creditLimit) {
    await notifyManagement("CREDIT", "Credit limit exceeded", `${customer.firstName} ${customer.lastName} exceeded the credit limit on a new order.`);
    throw await failure("This order would exceed the customer credit limit.");
  }
  const created = await prisma.order.create({
    data: {
      reference: await nextOrderRef(),
      customerId,
      status: "NEW",
      region: customer.region,
      total,
      createdById: user.id,
      lines: { create: lines },
    },
  });
  await logActivity(user, "orders", "create", created.reference, created.id);
  await notifyManagement("NEW_ORDER", `New order ${created.reference}`, `${customer.firstName} ${customer.lastName} has a new order.`);
  await notifyCustomer(customerId, "ORDER", `Order ${created.reference} received`, "Your order was recorded and is awaiting review.");
  revalidatePath("/orders");
  redirect(`/orders/${created.id}`);
}

export async function applyOrderStatus(id: string, status: string, formData: FormData) {
  await setOrderStatus(id, status, formData);
}

export async function setOrderStatus(id: string, status: string, formData: FormData) {
  const user = await actor("orders", "edit", formData);
  const reason = formData.get("reason");
  const previous = await prisma.order.findUnique({ where: { id }, include: { customer: true } });
  if (!previous) return;
  await prisma.order.update({
    where: { id },
    data: { status, nonDeliveryReason: reason == null ? previous.nonDeliveryReason : String(reason) },
  });
  await logActivity(user, "orders", "edit", previous.reference, id, previous.status, status);
  await notifyCustomer(previous.customerId, "ORDER", `Order ${previous.reference} ${status.toLowerCase()}`, `Your order status is now ${status.replaceAll("_", " ").toLowerCase()}.`);
  revalidatePath("/orders");
  revalidatePath(`/orders/${id}`);
}

export async function rejectOrder(id: string, formData: FormData) {
  // The rejection reason is the motif of this change.
  if (!formData.get("motif")) formData.set("motif", String(formData.get("reason") ?? ""));
  await setOrderStatus(id, "REJECTED", formData);
}

export async function updateOrderQuantities(id: string, formData: FormData) {
  const user = await actor("orders", "edit", formData);
  const order = await prisma.order.findUnique({ where: { id }, include: { lines: true, customer: true } });
  if (!order || ["SCHEDULED", "DELIVERED", "RECONCILED"].includes(order.status)) {
    throw await failure("Quantities can only be edited before scheduling.");
  }
  let total = 0;
  for (const line of order.lines) {
    const quantity = Number(formData.get(`qty:${line.id}`) ?? line.quantity);
    const unitPrice = await unitPriceFor(line.productId, order.customer.accountType, quantity);
    total += quantity * unitPrice;
    await prisma.orderLine.update({ where: { id: line.id }, data: { quantity, unitPrice } });
  }
  await prisma.order.update({ where: { id }, data: { total } });
  await logActivity(user, "orders", "edit", `${order.reference} quantities`, id);
  revalidatePath(`/orders/${id}`);
}

export async function recordPartialDelivery(id: string, formData: FormData) {
  const user = await actor("orders", "edit", formData);
  const order = await prisma.order.findUnique({ where: { id }, include: { lines: true } });
  if (!order) return;
  let all = true;
  let any = false;
  for (const line of order.lines) {
    const deliveredQty = Number(formData.get(`delivered:${line.id}`) ?? line.deliveredQty);
    await prisma.orderLine.update({ where: { id: line.id }, data: { deliveredQty } });
    if (deliveredQty > 0) any = true;
    if (deliveredQty < line.quantity) all = false;
  }
  const status = all ? "DELIVERED" : any ? "PARTIAL" : order.status;
  await prisma.order.update({ where: { id }, data: { status } });
  const note = await prisma.deliveryNote.findFirst({ where: { orderId: id }, include: { lines: true } });
  if (note && note.status !== "VALIDATED") {
    for (const noteLine of note.lines) {
      const orderLine = order.lines.find((line) => line.productId === noteLine.productId);
      if (!orderLine) continue;
      const deliveredQty = Number(formData.get(`delivered:${orderLine.id}`) ?? orderLine.deliveredQty);
      await prisma.deliveryNoteLine.update({
        where: { id: noteLine.id },
        data: { quantity: deliveredQty, lineAmount: deliveredQty * noteLine.unitPrice },
      });
    }
    const updated = await prisma.deliveryNote.findUnique({ where: { id: note.id }, include: { lines: true } });
    if (updated) {
      await prisma.deliveryNote.update({
        where: { id: note.id },
        data: { total: updated.lines.reduce((sum, line) => sum + line.lineAmount, 0) },
      });
    }
  }
  await logActivity(user, "orders", "edit", `${order.reference} delivery`, id, order.status, status);
  await notifyCustomer(order.customerId, "ORDER", `Delivery update ${order.reference}`, all ? "Your order was delivered in full." : "A partial delivery was recorded.");
  revalidatePath(`/orders/${id}`);
}
