import test from "node:test";
import assert from "node:assert/strict";
import { quote, type QuoteInput } from "../src/lib/pricing";
import { batchState, depositPerUnit, balanceDueDate, remainingCapacity } from "../src/lib/preorder";

const zone = { fee: 80, freeOver: 6000 };
const methods = { full: true, deposit: true, cod: true };
const due = new Date("2026-12-02T00:00:00Z");
const preBatch = (over: Partial<NonNullable<QuoteInput["lines"][number]["batch"]>> = {}) => ({ paymentOption: "DEPOSIT_OPTIONAL" as const, depositType: "PERCENT" as const, depositValue: 30, balanceDueAt: due, ...over });
const base = (over: Partial<QuoteInput>): QuoteInput => ({ lines: [], zone, discount: null, plan: "FULL", splitShipment: false, methods, ...over });

test("full payment of ready stock: due now equals total", () => {
  const q = quote(base({ lines: [{ key: "a", unitPrice: 2850, qty: 1, mode: "READY_STOCK" }] }));
  assert.equal(q.subtotal, 2850); assert.equal(q.shipping, 80); assert.equal(q.total, 2930); assert.equal(q.dueNow, 2930); assert.equal(q.balance, 0); assert.deepEqual(q.errors, []);
});
test("30% deposit on a ৳3,450 pre-order: deposit + delivery now, rest later", () => {
  const q = quote(base({ plan: "DEPOSIT", lines: [{ key: "t", unitPrice: 3450, qty: 1, mode: "PRE_ORDER", batch: preBatch() }] }));
  assert.equal(q.dueNow, 1035 + 80); assert.equal(q.balance, 3530 - 1115); assert.equal(q.balance, 2415); assert.equal(q.balanceDueAt?.getTime(), due.getTime());
});
test("deposit + balance always add up to the total", () => {
  for (const price of [890, 1250, 2950, 3450, 3850]) for (const qty of [1, 2, 3]) for (const pct of [10, 30, 33, 50]) {
    const q = quote(base({ plan: "DEPOSIT", lines: [{ key: "x", unitPrice: price, qty, mode: "PRE_ORDER", batch: preBatch({ depositValue: pct }) }] }));
    assert.equal(q.dueNow + q.balance, q.total, `${price}x${qty} @${pct}%`);
  }
});
test("mixed cart: ready items and delivery are paid now, only pre-order items get a deposit", () => {
  const q = quote(base({ plan: "DEPOSIT", lines: [{ key: "r", unitPrice: 2250, qty: 1, mode: "READY_STOCK" }, { key: "p", unitPrice: 3850, qty: 1, mode: "PRE_ORDER", batch: preBatch({ depositValue: 40 }) }] }));
  assert.equal(q.mixed, true); assert.equal(q.dueNow, 2250 + 1540 + 0 /*free over 6000 => 6100*/ ); assert.equal(q.shipping, 0); assert.equal(q.total, 6100); assert.equal(q.balance, 2310);
});
test("two-parcel choice doubles the delivery fee for mixed carts only", () => {
  const lines = [{ key: "r", unitPrice: 1000, qty: 1, mode: "READY_STOCK" as const }, { key: "p", unitPrice: 1000, qty: 1, mode: "PRE_ORDER" as const, batch: preBatch() }];
  assert.equal(quote(base({ lines, splitShipment: true })).shipping, 160);
  assert.equal(quote(base({ lines, splitShipment: false })).shipping, 80);
  assert.equal(quote(base({ lines: [lines[0]], splitShipment: true })).shipping, 80);
});
test("free delivery threshold uses the subtotal after discount", () => {
  const lines = [{ key: "a", unitPrice: 6200, qty: 1, mode: "READY_STOCK" as const }];
  assert.equal(quote(base({ lines })).shipping, 0);
  assert.equal(quote(base({ lines, discount: { type: "PERCENT", value: 10, minSubtotal: 0 } })).shipping, 80); // 5580 < 6000
});
test("discounts: percent rounds down, fixed is capped at subtotal, minimum enforced", () => {
  const lines = [{ key: "a", unitPrice: 2999, qty: 1, mode: "READY_STOCK" as const }];
  assert.equal(quote(base({ lines, discount: { type: "PERCENT", value: 10, minSubtotal: 0 } })).discount, 299);
  assert.equal(quote(base({ lines, discount: { type: "FIXED", value: 99999, minSubtotal: 0 } })).discount, 2999);
  const low = quote(base({ lines, discount: { type: "PERCENT", value: 10, minSubtotal: 5000 } }));
  assert.equal(low.discount, 0); assert.ok(low.errors[0].includes("5,000"));
});
test("COD is never offered when the bag has a pre-order item, and deposits need a depositable batch", () => {
  const pre = quote(base({ plan: "COD", lines: [{ key: "p", unitPrice: 3000, qty: 1, mode: "PRE_ORDER", batch: preBatch() }] }));
  assert.ok(!pre.allowedPlans.includes("COD")); assert.ok(pre.errors.length > 0);
  const fullOnly = quote(base({ plan: "DEPOSIT", lines: [{ key: "p", unitPrice: 950, qty: 1, mode: "PRE_ORDER", batch: preBatch({ paymentOption: "FULL_ONLY" }) }] }));
  assert.ok(!fullOnly.allowedPlans.includes("DEPOSIT"));
});
test("a batch requiring a deposit removes the pay-in-full option", () => {
  const q = quote(base({ lines: [{ key: "p", unitPrice: 2650, qty: 1, mode: "PRE_ORDER", batch: preBatch({ paymentOption: "DEPOSIT_REQUIRED", depositType: "FIXED", depositValue: 1000 }) }], plan: "DEPOSIT" }));
  assert.deepEqual(q.allowedPlans, ["DEPOSIT"]); assert.equal(q.dueNow, 1000 + 80);
});
test("COD collects everything on delivery", () => {
  const q = quote(base({ plan: "COD", lines: [{ key: "a", unitPrice: 2250, qty: 2, mode: "READY_STOCK" }] }));
  assert.equal(q.dueNow, 0); assert.equal(q.balance, q.total);
});
test("batch state, capacity and deposit helpers", () => {
  const now = new Date("2026-10-21T00:00:00Z");
  const b = { opensAt: new Date("2026-10-01"), closesAt: new Date("2026-11-01"), capacity: 10, heldQty: 4, committedQty: 6, manuallyClosed: false, paymentOption: "DEPOSIT_OPTIONAL" as const, depositType: "PERCENT" as const, depositValue: 30, dispatchFrom: new Date("2026-12-05"), dispatchTo: new Date("2026-12-15"), balanceDueDays: 3 };
  assert.equal(remainingCapacity(b), 0); assert.equal(batchState(b, now), "SOLD_OUT");
  assert.equal(batchState({ ...b, heldQty: 0, committedQty: 0 }, now), "OPEN");
  assert.equal(batchState({ ...b, heldQty: 0, committedQty: 0, manuallyClosed: true }, now), "CLOSED");
  assert.equal(batchState({ ...b, opensAt: new Date("2026-11-20"), closesAt: new Date("2026-12-01") }, now), "SCHEDULED");
  assert.equal(batchState({ ...b, closesAt: new Date("2026-10-10") }, now), "CLOSED");
  assert.equal(depositPerUnit(b, 3450), 1035); assert.equal(depositPerUnit({ ...b, depositType: "FIXED", depositValue: 99999 }, 1000), 1000); assert.equal(depositPerUnit({ ...b, paymentOption: "FULL_ONLY" }, 950), 950);
  assert.equal(balanceDueDate(b).toISOString().slice(0, 10), "2026-12-02");
});
