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 randomInvoiceNumber() {
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  let out = "INV-";
  for (let i = 0; i < 8; i += 1) out += alphabet[Math.floor(Math.random() * alphabet.length)];
  return out;
}

async function ensureInvoiceForSale(tx, sale, lines) {
  const existing = await tx.invoice.findFirst({ where: { saleId: sale.id, deletedAt: null } });
  if (existing) return existing;
  let number = randomInvoiceNumber();
  for (let i = 0; i < 10; i += 1) {
    const taken = await tx.invoice.findUnique({ where: { number } });
    if (!taken) break;
    number = randomInvoiceNumber();
  }
  const invoiceData = {
    number,
    status: "Issued",
    issueDate: sale.saleDate,
    subtotal: sale.subtotal,
    taxTotal: sale.taxTotal,
    total: sale.total,
    sale: { connect: { id: sale.id } },
    lines: {
      create: lines.map((l) => ({
        productId: l.productId,
        quantity: l.quantity,
        unitPrice: l.unitPrice,
        taxRate: l.taxRate,
        lineTotal: round2(l.quantity * l.unitPrice * (1 + l.taxRate / 100)),
      })),
    },
  };
  if (sale.branchId) invoiceData.branch = { connect: { id: sale.branchId } };
  if (sale.customerId) invoiceData.customer = { connect: { id: sale.customerId } };
  return tx.invoice.create({ data: invoiceData });
}

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

function randomNumber() {
  const alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  let out = "SAL-";
  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,
      unitPrice: Number(l?.unitPrice) || 0,
      taxRate: Number(l?.taxRate) || 0,
    }))
    .filter((l) => l.productId && l.quantity > 0);
}

function parseSaleTaxes(body) {
  if (!Array.isArray(body?.taxes)) return null;
  const rows = body.taxes
    .map((t) => ({
      taxId: t?.taxId ? String(t.taxId).trim() : null,
      name: String(t?.name || "Tax").trim() || "Tax",
      rate: Number(t?.rate) || 0,
    }))
    .filter((t) => t.rate > 0);
  return rows.length ? rows : null;
}

function calcTotals(lines, saleTaxes = null) {
  let subtotal = 0;
  for (const l of lines) {
    const qty = Math.max(0, Math.floor(Number(l.quantity) || 0));
    const price = Number(l.unitPrice) || 0;
    subtotal += qty * price;
  }
  subtotal = round2(subtotal);

  if (saleTaxes && saleTaxes.length > 0) {
    const taxBreakdown = saleTaxes.map((t) => ({
      taxId: t.taxId,
      name: t.name,
      rate: t.rate,
      amount: round2(subtotal * (t.rate / 100)),
    }));
    const taxTotal = round2(taxBreakdown.reduce((s, t) => s + t.amount, 0));
    return {
      subtotal,
      taxTotal,
      total: round2(subtotal + taxTotal),
      taxBreakdown,
      useSaleTaxes: true,
    };
  }

  let taxTotal = 0;
  for (const l of lines) {
    const qty = Math.max(0, Math.floor(Number(l.quantity) || 0));
    const price = Number(l.unitPrice) || 0;
    const rate = Number(l.taxRate) || 0;
    const base = qty * price;
    taxTotal += base * (rate / 100);
  }
  return {
    subtotal,
    taxTotal: round2(taxTotal),
    total: round2(subtotal + taxTotal),
    taxBreakdown: null,
    useSaleTaxes: false,
  };
}

function lineCreateData(l, totalsMeta) {
  const qty = Math.max(0, Math.floor(Number(l.quantity) || 0));
  const price = Number(l.unitPrice) || 0;
  const base = round2(qty * price);
  if (totalsMeta.useSaleTaxes) {
    return {
      productId: l.productId,
      quantity: qty,
      unitPrice: price,
      taxRate: 0,
      lineTotal: base,
    };
  }
  const rate = Number(l.taxRate) || 0;
  return {
    productId: l.productId,
    quantity: qty,
    unitPrice: price,
    taxRate: rate,
    lineTotal: round2(base * (1 + rate / 100)),
  };
}

function parsePaymentFields(body, total) {
  if (body.amountPaid === undefined || body.amountPaid === null || body.amountPaid === "") {
    return { amountPaid: null, changeGiven: null };
  }
  const amountPaid = round2(Number(body.amountPaid));
  if (!Number.isFinite(amountPaid) || amountPaid < 0) {
    return { amountPaid: null, changeGiven: null };
  }
  const changeGiven = round2(Math.max(0, amountPaid - total));
  return { amountPaid, changeGiven };
}

/** Prisma 7 SaleCreateInput uses relation connect, not scalar FK fields. */
function saleRelationFields(body, branchId, warehouseId) {
  const customerId = normalizeOptionalString(body.customerId);
  const paymentMethodId = normalizeOptionalString(body.paymentMethodId);
  const rel = {
    branch: { connect: { id: branchId } },
  };
  if (customerId) rel.customer = { connect: { id: customerId } };
  if (warehouseId) rel.warehouse = { connect: { id: warehouseId } };
  if (paymentMethodId) rel.paymentMethod = { connect: { id: paymentMethodId } };
  return rel;
}

function applyOptionalSaleRelationUpdates(data, body) {
  if (body.customerId !== undefined) {
    const customerId = normalizeOptionalString(body.customerId);
    data.customer = customerId ? { connect: { id: customerId } } : { disconnect: true };
  }
  if (body.warehouseId !== undefined) {
    const warehouseId = normalizeOptionalString(body.warehouseId);
    data.warehouse = warehouseId ? { connect: { id: warehouseId } } : { disconnect: true };
  }
  if (body.paymentMethodId !== undefined) {
    const paymentMethodId = normalizeOptionalString(body.paymentMethodId);
    data.paymentMethod = paymentMethodId ? { connect: { id: paymentMethodId } } : { disconnect: true };
  }
}

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.sale.count({ where }),
      prisma.sale.findMany({
        where,
        orderBy: [{ updatedAt: "desc" }],
        skip,
        take: limit,
        include: {
          customer: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          paymentMethod: { select: { id: true, name: true } },
          _count: { select: { lines: true } },
        },
      }),
    ]);

    const data = rows.map((r) => ({
      id: r.id,
      number: r.number,
      status: r.status,
      saleDate: r.saleDate,
      customerId: r.customerId,
      customerName: r.customer?.name || null,
      warehouseId: r.warehouseId,
      warehouseName: r.warehouse?.name || null,
      paymentMethodId: r.paymentMethodId,
      paymentMethodName: r.paymentMethod?.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.sale.findUnique({ where: { number } });
      if (!exists) return res.json({ number });
    }
    return res.status(503).json({ error: "Could not generate sale number. Try again." });
  } catch (err) {
    return next(err);
  }
});

router.get("/:id", requireAuth, requireActiveBranch, requirePosRead, async (req, res, next) => {
  try {
    const row = await prisma.sale.findFirst({
      where: { id: req.params.id, ...notDeleted, branchId: req.activeBranchId },
        include: {
          branch: { select: { id: true, name: true, address: true, city: true, phone: true, phones: true } },
          customer: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          paymentMethod: { select: { id: true, name: true } },
          invoices: { where: notDeleted, select: { id: true, number: true, status: 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", "Completed", "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 saleTaxes = parseSaleTaxes(body);
    const totals = calcTotals(lines, saleTaxes);
    const payment = parsePaymentFields(body, totals.total);

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

    const row = await prisma.$transaction(async (tx) => {
      const created = await tx.sale.create({
        data: {
          number,
          status,
          saleDate: Number.isNaN(saleDate.getTime()) ? new Date() : saleDate,
          notes: normalizeOptionalString(body.notes),
          subtotal: totals.subtotal,
          taxTotal: totals.taxTotal,
          total: totals.total,
          taxBreakdown: totals.taxBreakdown || undefined,
          amountPaid: payment.amountPaid,
          changeGiven: payment.changeGiven,
          ...saleRelationFields(body, req.activeBranchId, warehouseId),
          lines: {
            create: lines.map((l) => lineCreateData(l, totals)),
          },
        },
        include: {
          customer: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          paymentMethod: { select: { id: true, name: true } },
          lines: { include: { product: { select: { id: true, name: true, sku: true } } } },
        },
      });

      if (status === "Completed") {
        await adjustStock(tx, lines, -1, warehouseId);
        if (body.createInvoice !== false) {
          await ensureInvoiceForSale(tx, created, lines);
        }
        if (req.user?.sub) {
          await tx.notification.create({
            data: {
              userId: req.user.sub,
              title: "Sale completed",
              message: `Sale ${created.number} was completed.`,
              type: "success",
              link: `/sales/${created.id}`,
            },
          });
        }
      }
      return created;
    });
    auditFromReq(req, { action: "create", entityType: "Sale", 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.sale.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.status !== undefined) {
      const status = String(body.status || "");
      if (!["Draft", "Completed", "Cancelled"].includes(status)) return res.status(400).json({ error: "Invalid status" });
      data.status = status;
    }
    if (body.saleDate !== undefined) {
      const d = body.saleDate ? new Date(body.saleDate) : null;
      data.saleDate = d && !Number.isNaN(d.getTime()) ? d : existing.saleDate;
    }
    if (body.notes !== undefined) data.notes = normalizeOptionalString(body.notes);
    applyOptionalSaleRelationUpdates(data, body);

    const lines = body.lines !== undefined ? parseLines(body.lines) : undefined;
    const newStatus = data.status || existing.status;
    const { apply, reverse } = stockTransition(existing.status, newStatus, "Completed");

    const row = await prisma.$transaction(async (tx) => {
      if (reverse) {
        const oldLines = await tx.saleLine.findMany({ where: { saleId: 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.saleLine.deleteMany({ where: { saleId: id } });
      }

      const saleTaxes = body.taxes !== undefined ? parseSaleTaxes(body) : null;
      const totals = lines ? calcTotals(lines, saleTaxes) : null;
      const payment =
        body.amountPaid !== undefined && totals
          ? parsePaymentFields(body, totals.total)
          : body.amountPaid !== undefined
            ? parsePaymentFields(body, existing.total)
            : null;
      const updated = await tx.sale.update({
        where: { id },
        data: {
          ...data,
          ...(totals
            ? {
                subtotal: totals.subtotal,
                taxTotal: totals.taxTotal,
                total: totals.total,
                taxBreakdown: totals.taxBreakdown || null,
              }
            : {}),
          ...(payment
            ? { amountPaid: payment.amountPaid, changeGiven: payment.changeGiven }
            : {}),
          ...(lines
            ? {
                lines: {
                  create: lines.map((l) => lineCreateData(l, totals)),
                },
              }
            : {}),
        },
        include: {
          customer: { select: { id: true, name: true } },
          warehouse: { select: { id: true, name: true } },
          paymentMethod: { 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);
        if (body.createInvoice !== false) {
          await ensureInvoiceForSale(tx, updated, updated.lines);
        }
      }
      return updated;
    });

    auditFromReq(req, { action: "update", entityType: "Sale", 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.sale.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 === "Completed") {
        await adjustStock(tx, row.lines, 1, row.warehouseId);
      }
      await tx.sale.update({ where: { id }, data: { deletedAt: new Date() } });
    });
    auditFromReq(req, { action: "delete", entityType: "Sale", entityId: id, summary: row.number });
    return res.json({ ok: true });
  } catch (err) {
    return next(err);
  }
});

module.exports = router;

