const { Router } = require("express");
const prisma = require("../prisma");
const { requireAuth } = require("../middleware/auth");
const { requireInventoryRead, requireInventoryWrite } = require("../middleware/perms");
const { adjustStock, transferStock } = require("../lib/inventory");
const { requireActiveBranch } = require("../middleware/branch");

const router = Router();

async function applyTransferStock(tx, oldStatus, newStatus, lines, fromId, toId) {
  const wasSent = oldStatus === "Sent" || oldStatus === "Received";
  const wasReceived = oldStatus === "Received";
  const isSent = newStatus === "Sent" || newStatus === "Received";
  const isReceived = newStatus === "Received";

  if (!wasSent && isSent) {
    await adjustStock(tx, lines, -1, fromId);
  }
  if (wasSent && !isSent && !wasReceived) {
    await adjustStock(tx, lines, 1, fromId);
  }
  if (!wasReceived && isReceived) {
    if (oldStatus === "Sent") {
      await adjustStock(tx, lines, 1, toId);
    } else {
      await transferStock(tx, lines, fromId, toId);
    }
  }
  if (wasReceived && !isReceived) {
    if (newStatus === "Sent") {
      await adjustStock(tx, lines, -1, toId);
    } else {
      await adjustStock(tx, lines, -1, toId);
      await adjustStock(tx, lines, 1, fromId);
    }
  }
}

function normalizeOptionalString(input) {
  if (input === undefined) return undefined;
  if (input === null || input === "") return null;
  const s = String(input).trim();
  return s || null;
}

function prismaConflict(err, res) {
  if (err.code === "P2002") {
    return res.status(409).json({ error: "Transfer reference already exists" });
  }
  return null;
}

function randomRef() {
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  let out = "TRF-";
  for (let i = 0; i < 8; i += 1) out += alphabet[Math.floor(Math.random() * alphabet.length)];
  return out;
}

function parseLines(lines) {
  if (!Array.isArray(lines)) return [];
  return lines
    .map((l) => ({
      productId: l?.productId ? String(l.productId).trim() : "",
      quantity: Number.isFinite(Number(l?.quantity)) ? Math.floor(Number(l.quantity)) : 0,
    }))
    .filter((l) => l.productId && l.quantity > 0);
}

router.get("/", requireAuth, requireActiveBranch, requireInventoryRead, async (req, res, next) => {
  try {
    const page = Math.max(1, parseInt(req.query.page) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 25));
    const skip = (page - 1) * limit;
    const search = (req.query.search || "").trim();

    const where = { branchId: req.activeBranchId };
    if (search) {
      where.OR = [
        { reference: { contains: search, mode: "insensitive" } },
      ];
    }

    const [total, rows] = await Promise.all([
      prisma.inventoryTransfer.count({ where }),
      prisma.inventoryTransfer.findMany({
        where,
        orderBy: [{ updatedAt: "desc" }],
        skip,
        take: limit,
        include: {
          fromWarehouse: { select: { id: true, name: true } },
          toWarehouse: { select: { id: true, name: true } },
          _count: { select: { lines: true } },
        },
      }),
    ]);

    const data = rows.map((r) => ({
      id: r.id,
      reference: r.reference,
      status: r.status,
      transferDate: r.transferDate,
      fromWarehouseId: r.fromWarehouseId,
      fromWarehouseName: r.fromWarehouse?.name || null,
      toWarehouseId: r.toWarehouseId,
      toWarehouseName: r.toWarehouse?.name || null,
      lineCount: r._count?.lines || 0,
      createdAt: r.createdAt,
      updatedAt: r.updatedAt,
    }));

    return res.json({
      data,
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    });
  } catch (err) {
    return next(err);
  }
});

router.get("/generate-reference", requireAuth, requireInventoryRead, async (req, res, next) => {
  try {
    for (let i = 0; i < 20; i += 1) {
      const reference = randomRef();
      const exists = await prisma.inventoryTransfer.findUnique({ where: { reference } });
      if (!exists) return res.json({ reference });
    }
    return res.status(503).json({ error: "Could not generate transfer reference. Try again." });
  } catch (err) {
    return next(err);
  }
});

router.get("/:id", requireAuth, requireActiveBranch, requireInventoryRead, async (req, res, next) => {
  try {
    const row = await prisma.inventoryTransfer.findFirst({
      where: { id: req.params.id, branchId: req.activeBranchId },
      include: {
        fromWarehouse: { select: { id: true, name: true } },
        toWarehouse: { select: { id: true, name: true } },
        lines: {
          orderBy: { id: "asc" },
          include: { product: { select: { id: true, name: true, sku: true } } },
        },
      },
    });
    if (!row) return res.status(404).json({ error: "Not found" });
    return res.json(row);
  } catch (err) {
    return next(err);
  }
});

router.post("/", requireAuth, requireActiveBranch, requireInventoryWrite, async (req, res, next) => {
  try {
    const body = req.body || {};
    const reference = String(body.reference || "").trim();
    if (!reference) return res.status(400).json({ error: "Missing reference" });

    const fromWarehouseId = String(body.fromWarehouseId || "").trim();
    const toWarehouseId = String(body.toWarehouseId || "").trim();
    if (!fromWarehouseId) return res.status(400).json({ error: "Missing from warehouse" });
    if (!toWarehouseId) return res.status(400).json({ error: "Missing to warehouse" });
    if (fromWarehouseId === toWarehouseId) return res.status(400).json({ error: "From and To warehouse cannot be the same" });

    const status = String(body.status || "Draft");
    if (!["Draft", "Sent", "Received", "Cancelled"].includes(status)) {
      return res.status(400).json({ error: "Invalid status" });
    }

    const lines = parseLines(body.lines);
    if (lines.length === 0) return res.status(400).json({ error: "Add at least one line item" });

    const transferDate = body.transferDate ? new Date(body.transferDate) : new Date();

    const row = await prisma.$transaction(async (tx) => {
      const created = await tx.inventoryTransfer.create({
        data: {
          reference,
          fromWarehouseId,
          toWarehouseId,
          branchId: req.activeBranchId,
          status,
          transferDate: Number.isNaN(transferDate.getTime()) ? new Date() : transferDate,
          notes: normalizeOptionalString(body.notes),
          lines: {
            create: lines.map((l) => ({
              productId: l.productId,
              quantity: l.quantity,
            })),
          },
        },
        include: {
          fromWarehouse: { select: { id: true, name: true } },
          toWarehouse: { select: { id: true, name: true } },
          lines: { include: { product: { select: { id: true, name: true, sku: true } } } },
        },
      });

      if (status === "Sent" || status === "Received") {
        await applyTransferStock(tx, "Draft", status, lines, fromWarehouseId, toWarehouseId);
      }
      return created;
    });
    return res.status(201).json(row);
  } catch (err) {
    const r = prismaConflict(err, res);
    if (r) return r;
    return next(err);
  }
});

router.patch("/:id", requireAuth, requireActiveBranch, requireInventoryWrite, async (req, res, next) => {
  try {
    const { id } = req.params;
    const existing = await prisma.inventoryTransfer.findFirst({ where: { id, branchId: req.activeBranchId } });
    if (!existing) return res.status(404).json({ error: "Not found" });

    const body = req.body || {};
    const data = {};

    if (body.reference !== undefined) {
      const reference = String(body.reference || "").trim();
      if (!reference) return res.status(400).json({ error: "Reference cannot be empty" });
      data.reference = reference;
    }
    if (body.fromWarehouseId !== undefined) {
      const v = String(body.fromWarehouseId || "").trim();
      if (!v) return res.status(400).json({ error: "From warehouse cannot be empty" });
      data.fromWarehouseId = v;
    }
    if (body.toWarehouseId !== undefined) {
      const v = String(body.toWarehouseId || "").trim();
      if (!v) return res.status(400).json({ error: "To warehouse cannot be empty" });
      data.toWarehouseId = v;
    }
    if (data.fromWarehouseId && data.toWarehouseId && data.fromWarehouseId === data.toWarehouseId) {
      return res.status(400).json({ error: "From and To warehouse cannot be the same" });
    }
    if (body.status !== undefined) {
      const status = String(body.status || "");
      if (!["Draft", "Sent", "Received", "Cancelled"].includes(status)) return res.status(400).json({ error: "Invalid status" });
      data.status = status;
    }
    if (body.transferDate !== undefined) {
      const d = body.transferDate ? new Date(body.transferDate) : null;
      data.transferDate = d && !Number.isNaN(d.getTime()) ? d : existing.transferDate;
    }
    if (body.notes !== undefined) data.notes = normalizeOptionalString(body.notes);

    const lines = body.lines !== undefined ? parseLines(body.lines) : undefined;
    const newStatus = data.status || existing.status;
    const row = await prisma.$transaction(async (tx) => {
      if (lines) {
        if (lines.length === 0) throw Object.assign(new Error("Add at least one line item"), { status: 400 });
        await tx.inventoryTransferLine.deleteMany({ where: { transferId: id } });
      }

      const updated = await tx.inventoryTransfer.update({
        where: { id },
        data: {
          ...data,
          ...(lines
            ? {
                lines: {
                  create: lines.map((l) => ({ productId: l.productId, quantity: l.quantity })),
                },
              }
            : {}),
        },
        include: {
          fromWarehouse: { select: { id: true, name: true } },
          toWarehouse: { select: { id: true, name: true } },
          lines: { include: { product: { select: { id: true, name: true, sku: true } } } },
        },
      });

      const fromId = updated.fromWarehouseId;
      const toId = updated.toWarehouseId;
      await applyTransferStock(tx, existing.status, newStatus, updated.lines, fromId, toId);

      return updated;
    });

    return res.json(row);
  } catch (err) {
    if (err?.status === 400) return res.status(400).json({ error: err.message });
    const r = prismaConflict(err, res);
    if (r) return r;
    return next(err);
  }
});

router.delete("/:id", requireAuth, requireActiveBranch, requireInventoryWrite, async (req, res, next) => {
  try {
    const { id } = req.params;
    const row = await prisma.inventoryTransfer.findFirst({
      where: { id, branchId: req.activeBranchId },
      include: { lines: true },
    });
    if (!row) return res.status(404).json({ error: "Not found" });

    await prisma.$transaction(async (tx) => {
      if (row.status === "Sent" || row.status === "Received") {
        await applyTransferStock(tx, row.status, "Cancelled", row.lines, row.fromWarehouseId, row.toWarehouseId);
      }
      await tx.inventoryTransfer.delete({ where: { id } });
    });
    return res.json({ ok: true });
  } catch (err) {
    return next(err);
  }
});

module.exports = router;

