const { Router } = require("express");
const prisma = require("../prisma");
const { requireAuth } = require("../middleware/auth");
const { requirePosRead, requirePosWrite } = require("../middleware/perms");
const { adjustStock, stockTransition } = require("../lib/inventory");
const { auditFromReq } = require("../lib/audit");
const { notDeleted } = require("../lib/softDelete");
const { requireActiveBranch } = require("../middleware/branch");

const router = Router();

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: "Purchase number already exists" });
  }
  return null;
}

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

function round2(n) {
  return Math.round((Number(n) + Number.EPSILON) * 100) / 100;
}

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,
      unitCost: Number(l?.unitCost) || 0,
      taxRate: Number(l?.taxRate) || 0,
    }))
    .filter((l) => l.productId && l.quantity > 0);
}

function calcTotals(lines) {
  let subtotal = 0;
  let taxTotal = 0;
  for (const l of lines) {
    const qty = Math.max(0, Math.floor(Number(l.quantity) || 0));
    const cost = Number(l.unitCost) || 0;
    const rate = Number(l.taxRate) || 0;
    const base = qty * cost;
    const tax = base * (rate / 100);
    subtotal += base;
    taxTotal += tax;
  }
  const total = subtotal + taxTotal;
  return {
    subtotal: round2(subtotal),
    taxTotal: round2(taxTotal),
    total: round2(total),
  };
}

router.get("/", requireAuth, requireActiveBranch, requirePosRead, 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 = { ...notDeleted, branchId: req.activeBranchId };
    if (search) {
      where.OR = [
        { number: { contains: search, mode: "insensitive" } },
      ];
    }

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

    const data = rows.map((r) => ({
      id: r.id,
      number: r.number,
      status: r.status,
      purchaseDate: r.purchaseDate,
      supplierId: r.supplierId,
      supplierName: r.supplier?.name || null,
      warehouseId: r.warehouseId,
      warehouseName: r.warehouse?.name || null,
      lineCount: r._count?.lines || 0,
      subtotal: r.subtotal,
      taxTotal: r.taxTotal,
      total: r.total,
      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-number", requireAuth, requirePosRead, async (req, res, next) => {
  try {
    for (let i = 0; i < 20; i += 1) {
      const number = randomNumber();
      const exists = await prisma.purchase.findUnique({ where: { number } });
      if (!exists) return res.json({ number });
    }
    return res.status(503).json({ error: "Could not generate purchase number. Try again." });
  } catch (err) {
    return next(err);
  }
});

router.get("/:id", requireAuth, requireActiveBranch, requirePosRead, async (req, res, next) => {
  try {
    const row = await prisma.purchase.findFirst({
      where: { id: req.params.id, ...notDeleted, branchId: req.activeBranchId },
      include: {
        supplier: { select: { id: true, name: true } },
        warehouse: { 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, requirePosWrite, async (req, res, next) => {
  try {
    const body = req.body || {};
    const number = String(body.number || "").trim();
    if (!number) return res.status(400).json({ error: "Missing number" });

    const status = String(body.status || "Draft");
    if (!["Draft", "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 totals = calcTotals(lines);

    const purchaseDate = body.purchaseDate ? new Date(body.purchaseDate) : new Date();
    const warehouseId = normalizeOptionalString(body.warehouseId);

    const row = await prisma.$transaction(async (tx) => {
      const created = await tx.purchase.create({
        data: {
          number,
          supplierId: normalizeOptionalString(body.supplierId),
          warehouseId,
          branchId: req.activeBranchId,
          status,
          purchaseDate: Number.isNaN(purchaseDate.getTime()) ? new Date() : purchaseDate,
          notes: normalizeOptionalString(body.notes),
          ...totals,
          lines: {
            create: lines.map((l) => ({
              productId: l.productId,
              quantity: l.quantity,
              unitCost: l.unitCost,
              taxRate: l.taxRate,
              lineTotal: round2(l.quantity * l.unitCost * (1 + l.taxRate / 100)),
            })),
          },
        },
        include: {
          supplier: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          lines: { include: { product: { select: { id: true, name: true, sku: true } } } },
        },
      });

      if (status === "Received") {
        await adjustStock(tx, lines, 1, warehouseId);
      }
      return created;
    });
    auditFromReq(req, { action: "create", entityType: "Purchase", entityId: row.id, summary: row.number });
    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, requirePosWrite, async (req, res, next) => {
  try {
    const { id } = req.params;
    const existing = await prisma.purchase.findFirst({ where: { id, ...notDeleted, branchId: req.activeBranchId } });
    if (!existing) return res.status(404).json({ error: "Not found" });

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

    if (body.number !== undefined) {
      const number = String(body.number || "").trim();
      if (!number) return res.status(400).json({ error: "Number cannot be empty" });
      data.number = number;
    }
    if (body.supplierId !== undefined) data.supplierId = normalizeOptionalString(body.supplierId);
    if (body.warehouseId !== undefined) data.warehouseId = normalizeOptionalString(body.warehouseId);
    if (body.status !== undefined) {
      const status = String(body.status || "");
      if (!["Draft", "Received", "Cancelled"].includes(status)) return res.status(400).json({ error: "Invalid status" });
      data.status = status;
    }
    if (body.purchaseDate !== undefined) {
      const d = body.purchaseDate ? new Date(body.purchaseDate) : null;
      data.purchaseDate = d && !Number.isNaN(d.getTime()) ? d : existing.purchaseDate;
    }
    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 { apply, reverse } = stockTransition(existing.status, newStatus, "Received");

    const row = await prisma.$transaction(async (tx) => {
      if (reverse) {
        const oldLines = await tx.purchaseLine.findMany({ where: { purchaseId: id } });
        await adjustStock(tx, oldLines, -1, existing.warehouseId);
      }

      if (lines) {
        if (lines.length === 0) throw Object.assign(new Error("Add at least one line item"), { status: 400 });
        await tx.purchaseLine.deleteMany({ where: { purchaseId: id } });
      }

      const totals = lines ? calcTotals(lines) : null;
      const updated = await tx.purchase.update({
        where: { id },
        data: {
          ...data,
          ...(totals ? totals : {}),
          ...(lines
            ? {
                lines: {
                  create: lines.map((l) => ({
                    productId: l.productId,
                    quantity: l.quantity,
                    unitCost: l.unitCost,
                    taxRate: l.taxRate,
                    lineTotal: round2(l.quantity * l.unitCost * (1 + l.taxRate / 100)),
                  })),
                },
              }
            : {}),
        },
        include: {
          supplier: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          lines: { include: { product: { select: { id: true, name: true, sku: true } } } },
        },
      });

      if (apply) {
        await adjustStock(tx, updated.lines, 1, updated.warehouseId);
      }
      return updated;
    });

    auditFromReq(req, { action: "update", entityType: "Purchase", entityId: row.id, summary: row.number });
    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, requirePosWrite, async (req, res, next) => {
  try {
    const { id } = req.params;
    const row = await prisma.purchase.findFirst({
      where: { id, ...notDeleted, branchId: req.activeBranchId },
      include: { lines: true },
    });
    if (!row) return res.status(404).json({ error: "Not found" });

    await prisma.$transaction(async (tx) => {
      if (row.status === "Received") {
        await adjustStock(tx, row.lines, -1, row.warehouseId);
      }
      await tx.purchase.update({ where: { id }, data: { deletedAt: new Date() } });
    });
    auditFromReq(req, { action: "delete", entityType: "Purchase", entityId: id, summary: row.number });
    return res.json({ ok: true });
  } catch (err) {
    return next(err);
  }
});

module.exports = router;

