"use server";

import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
import { logActivity } from "@/lib/activity";
import { notifyCustomer } from "@/lib/notify";
import { unitPriceFor } from "@/lib/pricing";
import { moveStock } from "@/lib/stock";
import { actor, failure } from "./guard";
import { nextOrderRef, readLines } from "./orders";

export async function createRound(formData: FormData) {
  const user = await actor("rounds", "create", formData);
  const kind = String(formData.get("kind") ?? "DELIVERY") === "PROSPECTING" ? "PROSPECTING" : "DELIVERY";
  const created = await prisma.deliveryRound.create({
    data: {
      kind,
      date: new Date(String(formData.get("date") ?? "")),
      agentId: String(formData.get("agentId") ?? ""),
      vehicleId: String(formData.get("vehicleId") ?? ""),
      region: String(formData.get("region") ?? ""),
      notes: String(formData.get("notes") ?? "") || null,
      status: "PLANNED",
    },
  });
  await logActivity(user, "rounds", "create", `${kind} ${created.region}`, created.id);
  revalidatePath("/rounds");
}

export async function assignOrders(roundId: string, formData: FormData) {
  const user = await actor("rounds", "edit");
  const orderIds = formData.getAll("orderId").map(String).filter(Boolean);
  const existing = await prisma.roundStop.count({ where: { roundId } });
  const round = await prisma.deliveryRound.findUnique({ where: { id: roundId } });
  if (round?.kind === "PROSPECTING") throw await failure("Prospecting rounds start empty. Create field orders instead.");
  let sequence = existing + 1;
  for (const orderId of orderIds) {
    await prisma.roundStop.create({ data: { roundId, orderId, sequence, status: "PENDING" } });
    await prisma.order.update({
      where: { id: orderId },
      data: { status: "SCHEDULED", agentId: round?.agentId },
    });
    const order = await prisma.order.findUnique({ where: { id: orderId } });
    if (order) {
      await notifyCustomer(order.customerId, "ORDER", `Delivery scheduled ${order.reference}`, "Your order has been scheduled into a delivery round.");
    }
    sequence += 1;
  }
  await logActivity(user, "rounds", "edit", "Assigned stops", roundId);
  revalidatePath("/rounds");
  revalidatePath(`/rounds/${roundId}`);
}

async function renumber(roundId: string, orderedIds: string[]) {
  for (let i = 0; i < orderedIds.length; i++) {
    await prisma.roundStop.update({ where: { id: orderedIds[i] }, data: { sequence: i + 1 } });
  }
}

/** Save the order typed in the position boxes (1st, 2nd, 3rd…). */
export async function reorderStops(roundId: string, formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const stops = await prisma.roundStop.findMany({ where: { roundId }, orderBy: { sequence: "asc" } });
  const before = stops.map((stop) => stop.sequence).join(",");
  const ranked = stops
    .map((stop, index) => ({ id: stop.id, position: Number(formData.get(`position:${stop.id}`) ?? stop.sequence) || stop.sequence, index }))
    .sort((a, b) => a.position - b.position || a.index - b.index);
  await renumber(roundId, ranked.map((stop) => stop.id));
  await logActivity(user, "rounds", "edit", "Stop order", roundId, before, ranked.map((stop) => stop.position).join(","));
  revalidatePath(`/rounds/${roundId}`);
}

/** Move one stop up or down by one place. */
export async function moveStop(stopId: string, direction: "up" | "down", formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const stop = await prisma.roundStop.findUnique({ where: { id: stopId } });
  if (!stop) return;
  const stops = await prisma.roundStop.findMany({ where: { roundId: stop.roundId }, orderBy: { sequence: "asc" } });
  const ids = stops.map((row) => row.id);
  const from = ids.indexOf(stopId);
  const to = direction === "up" ? from - 1 : from + 1;
  if (to < 0 || to >= ids.length) return;
  [ids[from], ids[to]] = [ids[to], ids[from]];
  await renumber(stop.roundId, ids);
  await logActivity(user, "rounds", "edit", "Stop order", stop.roundId, String(from + 1), String(to + 1));
  revalidatePath(`/rounds/${stop.roundId}`);
}

export async function removeStop(stopId: string, formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const stop = await prisma.roundStop.delete({ where: { id: stopId } });
  await prisma.order.update({ where: { id: stop.orderId }, data: { status: "ACCEPTED", agentId: null } });
  const rest = await prisma.roundStop.findMany({ where: { roundId: stop.roundId }, orderBy: { sequence: "asc" } });
  await renumber(stop.roundId, rest.map((row) => row.id));
  await logActivity(user, "rounds", "edit", "Removed stop", stop.roundId);
  revalidatePath("/rounds");
  revalidatePath(`/rounds/${stop.roundId}`);
}

export async function setRoundStatus(id: string, status: string, formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const previous = await prisma.deliveryRound.findUnique({ where: { id } });
  await prisma.deliveryRound.update({ where: { id }, data: { status } });
  await logActivity(user, "rounds", "edit", `Round ${previous?.region}`, id, previous?.status, status);
  revalidatePath("/rounds");
  revalidatePath(`/rounds/${id}`);
}

export async function setStopStatus(id: string, status: string, formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const previous = await prisma.roundStop.findUnique({ where: { id } });
  const stop = await prisma.roundStop.update({
    where: { id },
    data: { status },
    include: { order: true, round: true },
  });
  if (status === "DELIVERED") {
    await prisma.order.update({ where: { id: stop.orderId }, data: { status: "DELIVERED" } });
  }
  await logActivity(user, "rounds", "edit", `Stop ${stop.order.reference}`, stop.orderId, previous?.status, status);
  revalidatePath(`/rounds/${stop.roundId}`);
}

export async function reassignRound(id: string, formData: FormData) {
  const user = await actor("rounds", "edit", formData);
  const agentId = String(formData.get("agentId") ?? "");
  const previous = await prisma.deliveryRound.findUnique({ where: { id }, include: { agent: true } });
  const round = await prisma.deliveryRound.update({ where: { id }, data: { agentId }, include: { agent: true } });
  await prisma.order.updateMany({
    where: { OR: [{ stop: { roundId: id } }, { sourceRoundId: id }] },
    data: { agentId },
  });
  await logActivity(
    user,
    "rounds",
    "edit",
    "Reassigned driver",
    id,
    previous ? `${previous.agent.firstName} ${previous.agent.lastName}` : undefined,
    `${round.agent.firstName} ${round.agent.lastName}`,
  );
  revalidatePath(`/rounds/${id}`);
}

/**
 * What happened at one stop: how much of each product was actually sold / delivered,
 * any extra products taken off the truck, and the money collected. Any part may be left empty,
 * so the driver can record a delivery without cash, or cash without changing the goods.
 */
export async function recordAtStop(stopId: string, formData: FormData) {
  const user = await actor("rounds", "edit");
  const stop = await prisma.roundStop.findUnique({
    where: { id: stopId },
    include: {
      round: { include: { loads: true, fieldOrders: { include: { lines: true } } } },
      order: { include: { customer: true, lines: true, deliveryNotes: true } },
    },
  });
  if (!stop) return;
  const { order, round } = stop;

  // 1. Quantities sold from what was already on the order.
  let delivered = 0;
  let ordered = 0;
  for (const line of order.lines) {
    const raw = formData.get(`sold:${line.id}`);
    const quantity = raw == null ? line.deliveredQty : Math.max(0, Math.min(line.quantity, Math.trunc(Number(raw) || 0)));
    if (quantity !== line.deliveredQty) {
      await prisma.orderLine.update({ where: { id: line.id }, data: { deliveredQty: quantity } });
    }
    delivered += quantity;
    ordered += line.quantity;
  }

  // 2. Extra products sold on the spot (van sales take them off the truck).
  const extra = await readLines(formData);
  if (extra.length) {
    const fromTruck = round.kind === "PROSPECTING";
    for (const item of extra) {
      if (fromTruck) {
        const load = round.loads.find((row) => row.productId === item.productId);
        const alreadySold = round.fieldOrders
          .flatMap((fieldOrder) => fieldOrder.lines)
          .filter((line) => line.productId === item.productId)
          .reduce((sum, line) => sum + line.deliveredQty, 0);
        const remaining = (load?.loadedQty ?? 0) - (load?.returnedQty ?? 0) - alreadySold;
        if (item.quantity > remaining) throw await failure("Not enough product left on the truck.");
      }
      const unitPrice = await unitPriceFor(item.productId, order.customer.accountType, item.quantity);
      const existing = order.lines.find((line) => line.productId === item.productId);
      if (existing) {
        await prisma.orderLine.update({
          where: { id: existing.id },
          data: { quantity: existing.quantity + item.quantity, deliveredQty: { increment: item.quantity }, unitPrice },
        });
      } else {
        await prisma.orderLine.create({
          data: { orderId: order.id, productId: item.productId, quantity: item.quantity, deliveredQty: item.quantity, unitPrice },
        });
      }
      delivered += item.quantity;
      ordered += item.quantity;
    }
  }

  // 3. Order total follows the lines; status follows what was delivered.
  const lines = await prisma.orderLine.findMany({ where: { orderId: order.id } });
  const total = lines.reduce((sum, line) => sum + line.quantity * line.unitPrice, 0);
  const status = delivered === 0 ? order.status : delivered >= ordered ? "DELIVERED" : "PARTIAL";
  await prisma.order.update({ where: { id: order.id }, data: { total, status } });
  if (delivered > 0 && stop.status !== "DELIVERED") {
    await prisma.roundStop.update({ where: { id: stopId }, data: { status: delivered >= ordered ? "DELIVERED" : "EN_ROUTE" } });
  }

  // 4. Money handed over, if any.
  const amount = Math.max(0, Number(formData.get("amount") ?? 0));
  if (amount > 0) {
    const note = order.deliveryNotes.find((row) => row.status !== "CANCELLED");
    const payment = await prisma.payment.create({
      data: {
        customerId: order.customerId,
        amount,
        method: String(formData.get("method") ?? "CASH"),
        agentId: round.agentId,
        orderId: order.id,
        deliveryNoteId: note?.id ?? null,
        documentRef: note?.number ?? order.reference,
      },
    });
    await prisma.order.update({ where: { id: order.id }, data: { paidAmount: { increment: amount } } });
    await logActivity(user, "payments", "create", `Payment ${payment.documentRef}`, order.id, undefined, `${amount} ${payment.method}`);
    await notifyCustomer(order.customerId, "PAYMENT", "Payment recorded", `A cash collection was recorded on ${order.reference}.`);
  }
  if (delivered > 0 || extra.length) {
    await logActivity(user, "rounds", "edit", `Stop ${order.reference}`, order.id, String(order.total), `${total} · ${delivered}/${ordered}`);
  }

  revalidatePath(`/rounds/${stop.roundId}`);
  revalidatePath(`/orders/${order.id}`);
  revalidatePath("/payments");
}

export async function loadTruck(roundId: string, formData: FormData) {
  const user = await actor("rounds", "edit");
  const round = await prisma.deliveryRound.findUnique({ where: { id: roundId } });
  if (!round || round.kind !== "PROSPECTING") throw await failure("Only prospecting rounds carry a truck load.");
  for (const { productId, quantity } of await readLines(formData)) {
    const product = await prisma.product.findUnique({ where: { id: productId } });
    if (!product || product.quantity < quantity) throw await failure("Not enough warehouse stock for {product}.", { product: product?.name ?? "—" });
    await moveStock({
      productId,
      delta: -quantity,
      type: "TRUCK_LOAD",
      reason: `Truck load ${round.region}`,
      reference: round.region,
      sourceId: roundId,
      userId: user.id,
    });
    const existing = await prisma.roundLoad.findFirst({ where: { roundId, productId } });
    if (existing) {
      await prisma.roundLoad.update({ where: { id: existing.id }, data: { loadedQty: { increment: quantity } } });
    } else {
      await prisma.roundLoad.create({ data: { roundId, productId, loadedQty: quantity } });
    }
  }
  await logActivity(user, "rounds", "edit", "Loaded truck", roundId);
  revalidatePath(`/rounds/${roundId}`);
  revalidatePath("/products");
  revalidatePath("/stock");
}

export async function returnTruckStock(roundId: string, formData: FormData) {
  const user = await actor("rounds", "edit");
  const loads = await prisma.roundLoad.findMany({ where: { roundId } });
  const round = await prisma.deliveryRound.findUnique({ where: { id: roundId } });
  for (const load of loads) {
    const returnedQty = Number(formData.get(`returned:${load.id}`) ?? load.returnedQty);
    const extra = returnedQty - load.returnedQty;
    await prisma.roundLoad.update({ where: { id: load.id }, data: { returnedQty } });
    if (extra !== 0) {
      await moveStock({
        productId: load.productId,
        delta: extra,
        type: "TRUCK_RETURN",
        reason: `Truck return ${round?.region ?? roundId}`,
        reference: round?.region,
        sourceId: roundId,
        userId: user.id,
      });
    }
  }
  await prisma.deliveryRound.update({ where: { id: roundId }, data: { status: "CLOSED" } });
  await logActivity(user, "rounds", "edit", "Returned truck stock", roundId);
  revalidatePath(`/rounds/${roundId}`);
  revalidatePath("/products");
  revalidatePath("/stock");
}

export async function createFieldOrder(roundId: string, formData: FormData) {
  const user = await actor("orders", "create");
  const round = await prisma.deliveryRound.findUnique({
    where: { id: roundId },
    include: { loads: true, fieldOrders: { include: { lines: true } } },
  });
  if (!round || round.kind !== "PROSPECTING") throw await failure("Field orders belong on a prospecting round.");
  const customerId = String(formData.get("customerId") ?? "");
  const customer = await prisma.customer.findUnique({ where: { id: customerId } });
  if (!customer) throw await failure("Customer not found");
  const settlement = String(formData.get("settlement") ?? "ORDER");
  const paidAmount = Number(formData.get("paidAmount") ?? 0);
  const fromTruck = formData.get("fromTruck") === "on" || settlement !== "ORDER";
  const lines = [];
  let total = 0;
  for (const { productId, quantity } of await readLines(formData)) {
    if (fromTruck) {
      const load = round.loads.find((item) => item.productId === productId);
      const alreadySold = round.fieldOrders
        .filter((order) => order.fromTruck)
        .flatMap((order) => order.lines)
        .filter((line) => line.productId === productId)
        .reduce((sum, line) => sum + line.quantity, 0);
      const remaining = (load?.loadedQty ?? 0) - (load?.returnedQty ?? 0) - alreadySold;
      if (quantity > remaining) throw await failure("Not enough product left on the truck.");
    }
    const unitPrice = await unitPriceFor(productId, customer.accountType, quantity);
    total += unitPrice * quantity;
    lines.push({ productId, quantity, unitPrice, deliveredQty: fromTruck ? quantity : 0 });
  }
  if (!lines.length) throw await failure("Add at least one product.");
  const status =
    settlement === "FULL_PAID" ? "RECONCILED" : fromTruck ? "DELIVERED" : "ACCEPTED";
  const created = await prisma.order.create({
    data: {
      reference: await nextOrderRef(),
      customerId,
      status,
      region: customer.region,
      agentId: round.agentId,
      total,
      createdById: user.id,
      sourceRoundId: roundId,
      settlement,
      paidAmount: settlement === "FULL_PAID" ? total : paidAmount,
      fromTruck,
      lines: { create: lines },
    },
  });
  const sequence = (await prisma.roundStop.count({ where: { roundId } })) + 1;
  await prisma.roundStop.create({
    data: { roundId, orderId: created.id, sequence, status: fromTruck ? "DELIVERED" : "PENDING" },
  });
  if (paidAmount > 0 || settlement === "FULL_PAID") {
    await prisma.payment.create({
      data: {
        customerId,
        amount: settlement === "FULL_PAID" ? total : paidAmount,
        method: "CASH",
        agentId: round.agentId,
        orderId: created.id,
        documentRef: created.reference,
      },
    });
    await notifyCustomer(customerId, "PAYMENT", "Payment recorded", `A cash collection was recorded on ${created.reference}.`);
  }
  await notifyCustomer(customerId, "ORDER", `Order ${created.reference}`, "An order was recorded from a field / van-sale round.");
  await logActivity(user, "rounds", "create", created.reference, created.id, undefined, settlement);
  revalidatePath(`/rounds/${roundId}`);
  revalidatePath("/orders");
  revalidatePath("/payments");
}
