/* Integration tests against the real database (uses DATABASE_URL). Creates and removes its own rows.
   Run: npm run test:int */
import "dotenv/config";
import test, { after } from "node:test";
import assert from "node:assert/strict";
import { db } from "../src/lib/db";
import { __test, cancelOrder, reviewPayment, submitPaymentProof, updateFulfillment, expireHolds, recordReceipt } from "../src/lib/orders";

const tag = `T${Date.now().toString(36).toUpperCase()}`;
const day = (n: number) => new Date(Date.now() + n * 86400000);
let productId = "", batchId = "", variantId = "", readyVariantId = "", staffId = "";

test("setup", async () => {
  const cat = await db.category.findFirstOrThrow();
  const p = await db.product.create({ data: { slug: `test-${tag.toLowerCase()}`, sku: `SKU-${tag}`, name: `Test ${tag}`, description: "test", basePrice: 1000, categoryId: cat.id, status: "PUBLISHED" } });
  productId = p.id;
  const b = await db.preorderBatch.create({ data: { productId, code: `B-${tag}`, opensAt: day(-1), closesAt: day(5), capacity: 5, dispatchFrom: day(20), dispatchTo: day(25), depositValue: 30 } });
  batchId = b.id;
  variantId = (await db.productVariant.create({ data: { productId, sku: `V-${tag}`, mode: "PRE_ORDER", batchId } })).id;
  readyVariantId = (await db.productVariant.create({ data: { productId, sku: `R-${tag}`, mode: "READY_STOCK", stockQty: 3 } })).id;
  staffId = (await db.user.create({ data: { email: `staff-${tag.toLowerCase()}@example.invalid`, name: "Test staff", passwordHash: "x", role: "MANAGER" } })).id;
});

test("40 concurrent reservations on a 5-place batch: exactly 5 succeed", async () => {
  const now = new Date();
  const results = await Promise.all(Array.from({ length: 40 }, () => db.$transaction((tx) => __test.holdBatch(tx, batchId, 1, now)).catch(() => false)));
  assert.equal(results.filter(Boolean).length, 5);
  const b = await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } });
  assert.equal(b.heldQty + b.committedQty, 5);
  await db.preorderBatch.update({ where: { id: batchId }, data: { heldQty: 0 } });
});

test("multi-unit requests never overshoot capacity", async () => {
  const now = new Date();
  const results = await Promise.all([3, 3, 2, 2, 1].map((q) => db.$transaction((tx) => __test.holdBatch(tx, batchId, q, now)).then((ok) => (ok ? q : 0))));
  const b = await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } });
  assert.ok(b.heldQty <= 5); assert.equal(b.heldQty, results.reduce((a, x) => a + x, 0));
  await db.preorderBatch.update({ where: { id: batchId }, data: { heldQty: 0 } });
});

test("ready stock: 10 concurrent buyers for 3 units", async () => {
  const r = await Promise.all(Array.from({ length: 10 }, () => db.$transaction((tx) => __test.takeStock(tx, readyVariantId, 1))));
  assert.equal(r.filter(Boolean).length, 3);
  assert.equal((await db.productVariant.findUniqueOrThrow({ where: { id: readyVariantId } })).stockQty, 0);
  await db.productVariant.update({ where: { id: readyVariantId }, data: { stockQty: 3 } });
});

test("closed or not-yet-open batches reject reservations", async () => {
  await db.preorderBatch.update({ where: { id: batchId }, data: { manuallyClosed: true } });
  assert.equal(await db.$transaction((tx) => __test.holdBatch(tx, batchId, 1, new Date())), false);
  await db.preorderBatch.update({ where: { id: batchId }, data: { manuallyClosed: false, opensAt: day(2) } });
  assert.equal(await db.$transaction((tx) => __test.holdBatch(tx, batchId, 1, new Date())), false);
  await db.preorderBatch.update({ where: { id: batchId }, data: { opensAt: day(-1) } });
});

async function makeOrder(qty: number, plan: "DEPOSIT" | "FULL", holdHoursAgo = 0) {
  await db.preorderBatch.update({ where: { id: batchId }, data: { heldQty: { increment: qty } } });
  const unit = 1000, subtotal = unit * qty, ship = 80, total = subtotal + ship, dueNow = plan === "DEPOSIT" ? Math.round(subtotal * 0.3) + ship : total;
  return db.order.create({ data: {
    number: `${tag}-${Math.random().toString(36).slice(2, 7)}`, trackingToken: `${tag}${Math.random().toString(36).slice(2)}`, customerName: "Test", phone: "01700000000", email: `t-${tag.toLowerCase()}@example.invalid`, address: "x", district: "Dhaka", area: "x", shippingZoneName: "Inside Dhaka",
    subtotal, shippingFee: ship, total, dueNow, paymentPlan: plan, paymentMethod: "BKASH", hasPreorder: true, holdExpiresAt: new Date(Date.now() - holdHoursAgo * 3600000 + (holdHoursAgo ? 0 : 86400000)),
    items: { create: [{ productId, variantId, categorySlug: "bags", productName: "Test", variantLabel: "", sku: `V-${tag}`, unitPrice: unit, qty, lineTotal: subtotal, mode: "PRE_ORDER", batchId, depositPerUnit: plan === "DEPOSIT" ? 300 : 0 }] },
  } });
}

test("deposit verification commits capacity; payment and fulfillment stay separate", async () => {
  const o = await makeOrder(2, "DEPOSIT");
  await assert.rejects(updateFulfillment(o.id, "CONFIRMED", staffId), /Verify the deposit/);
  await submitPaymentProof(o.trackingToken, { amount: o.dueNow, reference: `TRX${tag}1`, senderNumber: "017", method: "BKASH" });
  let cur = await db.order.findUniqueOrThrow({ where: { id: o.id }, include: { payments: true, items: true } });
  assert.equal(cur.paymentStatus, "PENDING_VERIFICATION"); assert.equal(cur.amountPaid, 0, "unverified money is not counted");
  await reviewPayment(cur.payments[0].id, true, staffId);
  cur = await db.order.findUniqueOrThrow({ where: { id: o.id }, include: { payments: true, items: true } });
  assert.equal(cur.amountPaid, o.dueNow); assert.equal(cur.paymentStatus, "PARTIALLY_PAID"); assert.equal(cur.fulfillmentStatus, "PENDING_CONFIRMATION", "verifying does not move fulfillment");
  assert.equal(cur.items[0].capacityState, "COMMITTED");
  const b = await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } });
  assert.equal(b.committedQty, 2); assert.equal(b.heldQty, 0);
  await updateFulfillment(o.id, "CONFIRMED", staffId);
  await assert.rejects(updateFulfillment(o.id, "SHIPPED", staffId, { courier: "X", trackingNumber: "1" }), /balance is still outstanding/);
  await recordReceipt(o.id, { kind: "BALANCE", method: "BKASH", amount: o.total - o.dueNow, idempotencyKey: `${tag}-bal` }, staffId);
  await recordReceipt(o.id, { kind: "BALANCE", method: "BKASH", amount: o.total - o.dueNow, idempotencyKey: `${tag}-bal` }, staffId); // retried: must not double count
  cur = await db.order.findUniqueOrThrow({ where: { id: o.id }, include: { payments: true, items: true } });
  assert.equal(cur.amountPaid, o.total); assert.equal(cur.paymentStatus, "PAID");
  await updateFulfillment(o.id, "SHIPPED", staffId, { courier: "Pathao", trackingNumber: "PX1" });
  await assert.rejects(updateFulfillment(o.id, "CONFIRMED", staffId), /forward only/);
});

test("duplicate transaction IDs are refused", async () => {
  const o = await makeOrder(1, "FULL");
  await assert.rejects(submitPaymentProof(o.trackingToken, { amount: 100, reference: `TRX${tag}1`, senderNumber: "017", method: "BKASH" }), /already been submitted/);
  await cancelOrder(o.id, "test", staffId);
});

test("cancelling releases capacity; refunds update the paid amount", async () => {
  const o = await makeOrder(1, "FULL");
  await recordReceipt(o.id, { kind: "FULL", method: "BKASH", amount: o.total, idempotencyKey: `${tag}-full` }, staffId);
  const before = await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } });
  await cancelOrder(o.id, "customer request", staffId);
  const after = await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } });
  assert.equal(after.committedQty, before.committedQty - 1);
  await recordReceipt(o.id, { kind: "REFUND", method: "BKASH", amount: o.total, idempotencyKey: `${tag}-ref` }, staffId);
  const cur = await db.order.findUniqueOrThrow({ where: { id: o.id } });
  assert.equal(cur.amountPaid, 0); assert.equal(cur.amountRefunded, o.total); assert.equal(cur.paymentStatus, "REFUNDED");
});

test("expired unpaid holds are released automatically", async () => {
  const o = await makeOrder(2, "DEPOSIT", 1); // hold ended an hour ago
  const heldBefore = (await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } })).heldQty;
  await expireHolds();
  const cur = await db.order.findUniqueOrThrow({ where: { id: o.id }, include: { items: true } });
  assert.equal(cur.fulfillmentStatus, "CANCELLED"); assert.equal(cur.items[0].capacityState, "RELEASED");
  assert.equal((await db.preorderBatch.findUniqueOrThrow({ where: { id: batchId } })).heldQty, heldBefore - 2);
});

after(async () => {
  await db.order.deleteMany({ where: { number: { startsWith: tag } } });
  await db.product.deleteMany({ where: { id: productId } });
  await db.user.deleteMany({ where: { id: staffId } });
  await db.$disconnect();
});
