import test from "node:test";
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import os from "node:os";
import path from "node:path";
process.env.NODE_ENV = "test";
const temp = mkdtempSync(path.join(os.tmpdir(), "stockroom-test-"));
process.env.DB_PATH = path.join(temp, "test.sqlite");
const { app } = await import("../server/index.js");
const { db, one, all } = await import("../server/db.js");
const server = app.listen(0, "127.0.0.1");
await new Promise((r) => server.once("listening", r));
const base = `http://127.0.0.1:${server.address().port}/api`;
function client() {
  let cookie = "";
  return async (url, body, method = "POST", key = crypto.randomUUID()) => {
    const r = await fetch(base + url, {
      method: body === undefined ? "GET" : method,
      headers: {
        "content-type": "application/json",
        "x-stockroom-client": "web",
        "idempotency-key": key,
        cookie,
      },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    if (r.headers.get("set-cookie"))
      cookie = r.headers.get("set-cookie").split(";")[0];
    return { status: r.status, ...(await r.json()) };
  };
}
const a = client(),
  b = client(),
  v = client(),
  op = client(),
  named = client();
const today = new Date().toISOString().slice(0, 10),
  future = new Date(Date.now() + 20 * 86400000).toISOString().slice(0, 10);
test("Stockroom integration: isolated accounts, stock ledger, transactions, purchasing, reconciliation and recovery", async (t) => {
  let state, p, lot, other, tenant, orderId, countId;
  await t.test("anonymous access and cross-site writes rejected", async () => {
    assert.equal((await a("/state")).status, 401);
    const r = await fetch(base + "/auth/demo", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: "{}",
    });
    assert.equal(r.status, 403);
    const origin = await fetch(base + "/auth/demo", {
      method: "POST",
      headers: {
        "content-type": "application/json",
        "x-stockroom-client": "web",
        origin: "https://attacker.example",
      },
      body: "{}",
    });
    assert.equal(origin.status, 403);
  });
  await t.test("independent seeded workspaces", async () => {
    const aa = await a("/auth/demo", {}),
      bb = await b("/auth/demo", {});
    assert.equal(aa.status, 200);
    assert.equal(bb.status, 200);
    assert.notEqual(aa.user.tenant, bb.user.tenant);
    tenant = aa.user.tenant;
    state = await a("/state");
    assert.equal(state.products.length, 36);
    assert.equal(state.locations.length, 5);
    assert.equal(state.suppliers.length, 4);
    assert.equal(state.sales.length, 1008);
    p = state.products.find((p) => p.sku === "SR-1001");
    lot = state.lots.find((l) => l.product === p.id);
    other = state.locations.find((l) => l.id !== lot.location);
    const cross = await b("/transfer", {
      lot: lot.id,
      destination: other.id,
      quantity: 1,
      note: "Cross tenant",
    });
    assert.equal(cross.status, 404);
  });
  await t.test(
    "invalid, negative, fractional, expired receipts do not change stock",
    async () => {
      const base = {
        product: p.id,
        location: lot.location,
        code: "INVALID",
        reference: "TEST",
        expiry: future,
      };
      for (const quantity of [-1, 0, 1.5, "nope"])
        assert.equal((await a("/receive", { ...base, quantity })).status, 400);
      assert.equal(
        (await a("/receive", { ...base, quantity: 1, expiry: "2020-01-01" }))
          .status,
        400,
      );
      assert.equal(
        (await a("/receive", { ...base, quantity: 1, expiry: "2026-02-30" }))
          .status,
        400,
      );
      assert.equal(
        (await a("/state")).products.find((x) => x.id === p.id).stock,
        p.stock,
      );
    },
  );
  await t.test(
    "receipt replay is idempotent and preserves durable stock",
    async () => {
      const body = {
        product: p.id,
        location: lot.location,
        quantity: 10,
        code: "QA-RECEIPT",
        reference: "TEST-RECEIPT",
        expiry: future,
      };
      const key = crypto.randomUUID();
      const first = await a("/receive", body, "POST", key),
        second = await a("/receive", body, "POST", key);
      assert.equal(first.status, 200);
      assert.deepEqual(first, second);
      assert.equal(
        (await a("/state")).products.find((x) => x.id === p.id).stock,
        p.stock + 10,
      );
      assert.equal(
        (await a("/receive", { ...body, quantity: 11 }, "POST", key)).status,
        409,
      );
    },
  );
  await t.test(
    "transfers preserve totals, lot identity and paired movement; concurrent overdraft blocked",
    async () => {
      const before = (await a("/state")).products.find(
        (x) => x.id === p.id,
      ).stock;
      const r = await a("/transfer", {
        lot: lot.id,
        destination: other.id,
        quantity: 1,
        note: "Fill shelf",
      });
      assert.equal(r.status, 200);
      const st = await a("/state");
      assert.equal(st.products.find((x) => x.id === p.id).stock, before);
      const ms = st.movements.filter((m) => m.reference === r.reference);
      assert.equal(ms.length, 2);
      assert.equal(
        ms.reduce((s, m) => s + m.quantity, 0),
        0,
      );
      assert.equal(
        st.lots.find(
          (l) =>
            l.location === other.id &&
            l.product === p.id &&
            l.code === lot.code,
        ).expiry,
        lot.expiry,
      );
      const tiny = st.lots.find(
        (l) =>
          l.location === other.id && l.product === p.id && l.code === lot.code,
      );
      const results = await Promise.all([
        a("/transfer", {
          lot: tiny.id,
          destination: lot.location,
          quantity: 1,
          note: "Concurrent A",
        }),
        a("/transfer", {
          lot: tiny.id,
          destination: lot.location,
          quantity: 1,
          note: "Concurrent B",
        }),
      ]);
      assert.deepEqual(results.map((r) => r.status).sort(), [200, 409]);
    },
  );
  await t.test(
    "count approval rejects stale stock and applies only current reviewed variance",
    async () => {
      const st = await a("/state");
      const fresh = st.lots.find((l) => l.code === "QA-RECEIPT");
      let r = await a("/counts", {
        lot: fresh.id,
        counted: 8,
        version: fresh.version,
        note: "Count before movement",
      });
      assert.equal(r.status, 200);
      countId = r.id;
      await a("/transfer", {
        lot: fresh.id,
        destination: other.id,
        quantity: 1,
        note: "Move during review",
      });
      assert.equal(
        (await a(`/counts/${countId}/review`, { action: "approve" })).status,
        409,
      );
      assert.equal(
        (await a(`/counts/${countId}/review`, { action: "reject" })).status,
        200,
      );
      const f = (await a("/state")).lots.find((l) => l.id === fresh.id);
      r = await a("/counts", {
        lot: f.id,
        counted: 7,
        version: f.version,
        note: "Fresh verified count",
      });
      assert.equal(r.status, 200);
      assert.equal(
        (await a(`/counts/${r.id}/review`, { action: "approve" })).status,
        200,
      );
      assert.equal(
        (await a("/state")).lots.find((l) => l.id === f.id).quantity,
        7,
      );
      assert.equal(
        (await a(`/counts/${r.id}/review`, { action: "approve" })).status,
        409,
      );
    },
  );
  await t.test("purchase lifecycle and over-receipt rollback", async () => {
    let r = await a("/orders", {
      supplier: p.supplier,
      expected: future,
      note: "QA order",
      items: [{ product: p.id, quantity: 5 }],
    });
    assert.equal(r.status, 200);
    orderId = r.id;
    let st = await a("/state");
    const item = st.orderItems.find((i) => i.order_id === orderId);
    const body = {
      product: p.id,
      location: lot.location,
      code: "QA-PO",
      expiry: future,
      reference: "QA-PACKING",
      order_item: item.id,
      quantity: 6,
    };
    assert.equal((await a("/receive", body)).status, 400);
    assert.equal(
      (await a(`/orders/${orderId}/status`, { status: "ordered" })).status,
      200,
    );
    assert.equal((await a("/receive", body)).status, 409);
    assert.equal((await a("/receive", { ...body, quantity: 2 })).status, 200);
    st = await a("/state");
    assert.equal(st.orders.find((o) => o.id === orderId).status, "partial");
    assert.equal(
      (await a(`/orders/${orderId}/status`, { status: "cancelled" })).status,
      409,
    );
    assert.equal((await a("/receive", { ...body, quantity: 3 })).status, 200);
    st = await a("/state");
    assert.equal(st.orders.find((o) => o.id === orderId).status, "received");
    assert.equal(st.orderItems.find((i) => i.id === item.id).received, 5);
    assert.equal((await a("/receive", { ...body, quantity: 1 })).status, 400);
  });
  await t.test(
    "sales import deducts once, captures shortages, retries and rolls back bad batches",
    async () => {
      const row = {
        external_id: "SALE-QA-1",
        sku: p.sku,
        location: lot.location,
        quantity: 1,
        revenue: p.price,
        date: today,
      };
      const before = (await a("/state")).products.find(
        (x) => x.id === p.id,
      ).stock;
      let r = await a("/sales/import", { rows: [row] });
      assert.equal(r.imported, 1);
      assert.equal((await a("/sales/import", { rows: [row] })).duplicates, 1);
      assert.equal(
        (await a("/sales/import", { rows: [{ ...row, quantity: 2 }] })).status,
        409,
      );
      assert.equal(
        (await a("/state")).products.find((x) => x.id === p.id).stock,
        before - 1,
      );
      const rollback = await a("/sales/import", {
        rows: [
          { ...row, external_id: "ROLLBACK" },
          { ...row, external_id: "BAD", sku: "UNKNOWN" },
        ],
      });
      assert.equal(rollback.status, 400);
      assert.equal(
        (await a("/state")).products.find((x) => x.id === p.id).stock,
        before - 1,
      );
      r = await a("/sales/import", {
        rows: [
          { ...row, external_id: "SHORT", location: other.id, quantity: 30 },
        ],
      });
      assert.equal(r.exceptions, 1);
      const s = (await a("/state")).sales.find(
        (s) => s.external_id === "SHORT",
      );
      assert.equal((await a(`/sales/${s.id}/retry`, {})).status, 409);
      await a("/receive", {
        product: p.id,
        location: other.id,
        quantity: 40,
        code: "QA-SALE",
        expiry: future,
        reference: "SALE-RETRY",
      });
      assert.equal((await a(`/sales/${s.id}/retry`, {})).status, 200);
      assert.equal((await a(`/sales/${s.id}/retry`, {})).status, 400);
    },
  );
  await t.test(
    "viewer and operator server-enforced permissions and revocation",
    async () => {
      const pwd = "TestingStockroomOnly2026!";
      let r = await a("/users", {
        name: "Viewer QA",
        email: "viewer@qa.example",
        password: pwd,
        role: "viewer",
      });
      assert.equal(r.status, 200);
      const viewerId = r.id;
      assert.equal(
        (await v("/auth/login", { email: "viewer@qa.example", password: pwd }))
          .status,
        200,
      );
      assert.equal((await v("/state")).status, 200);
      assert.equal(
        (
          await v("/receive", {
            product: p.id,
            location: lot.location,
            quantity: 1,
            code: "FORBIDDEN",
            reference: "NO",
          })
        ).status,
        403,
      );
      assert.equal((await v("/export")).status, 403);
      r = await a("/users", {
        name: "Operator QA",
        email: "operator@qa.example",
        password: pwd,
        role: "operator",
      });
      assert.equal(r.status, 200);
      assert.equal(
        (
          await op("/auth/login", {
            email: "operator@qa.example",
            password: pwd,
          })
        ).status,
        200,
      );
      assert.equal(
        (
          await op("/receive", {
            product: p.id,
            location: lot.location,
            quantity: 1,
            code: "OP",
            reference: "OP",
          })
        ).status,
        200,
      );
      assert.equal(
        (await op("/waste", { lot: lot.id, quantity: 1, note: "No" })).status,
        403,
      );
      assert.equal(
        (await op(`/counts/${countId}/review`, { action: "approve" })).status,
        403,
      );
      assert.equal(
        (
          await a(
            `/users/${viewerId}`,
            { role: "viewer", active: false },
            "PATCH",
          )
        ).status,
        200,
      );
      assert.equal((await v("/state")).status, 401);
    },
  );
  await t.test(
    "waste requires a reason and cannot exceed available stock",
    async () => {
      assert.equal(
        (await a("/waste", { lot: lot.id, quantity: 1, note: "" })).status,
        400,
      );
      assert.equal(
        (
          await a("/waste", {
            lot: lot.id,
            quantity: 100000,
            note: "Overdraft",
          })
        ).status,
        409,
      );
      const available = (await a("/state")).lots.find((l) => l.id === lot.id);
      if (available.quantity > 0)
        assert.equal(
          (
            await a("/waste", {
              lot: lot.id,
              quantity: 1,
              note: "Damaged packaging",
            })
          ).status,
          200,
        );
    },
  );
  await t.test(
    "registration, password change, logout and single-use recovery",
    async () => {
      const pwd = "TestAccountPassword2026!";
      const r = await named("/auth/register", {
        name: "Customer QA",
        business: "QA Market",
        email: "customer@qa.example",
        password: pwd,
      });
      assert.equal(r.status, 200);
      assert.ok(r.recovery);
      assert.equal(
        (
          await named("/auth/password", {
            current: "wrong",
            password: pwd + "x",
          })
        ).status,
        400,
      );
      assert.equal(
        (await named("/auth/password", { current: pwd, password: pwd + "x" }))
          .status,
        200,
      );
      assert.equal((await named("/auth/logout", {})).status, 200);
      assert.equal((await named("/state")).status, 401);
      assert.equal(
        (
          await named("/auth/login", {
            email: "customer@qa.example",
            password: pwd,
          })
        ).status,
        401,
      );
      const recovered = await named("/auth/recover", {
        email: "customer@qa.example",
        recovery: r.recovery,
        password: pwd + "new",
      });
      assert.equal(recovered.status, 200);
      assert.ok(recovered.recovery);
      assert.equal(
        (
          await named("/auth/recover", {
            email: "customer@qa.example",
            recovery: r.recovery,
            password: pwd,
          })
        ).status,
        400,
      );
      assert.equal(
        (
          await named("/auth/login", {
            email: "customer@qa.example",
            password: pwd + "new",
          })
        ).status,
        200,
      );
    },
  );
  await t.test(
    "export excludes secrets; ledger balances match every lot; database integrity",
    async () => {
      const exported = await a("/export");
      assert.equal(exported.format, "stockroom-export-v1");
      assert.ok(!exported.users && !exported.sessions && !exported.requests);
      for (const l of all("SELECT * FROM lots WHERE tenant=?", tenant)) {
        const sum = one(
          "SELECT SUM(quantity) quantity FROM movements WHERE lot=?",
          l.id,
        ).quantity;
        assert.equal(l.quantity, sum, `Ledger drift in lot ${l.id}`);
      }
      assert.equal(one("PRAGMA integrity_check").integrity_check, "ok");
      assert.deepEqual(all("PRAGMA foreign_key_check"), []);
    },
  );
});
test.after(async () => {
  await new Promise((r) => server.close(r));
  db.close();
  rmSync(temp, { recursive: true, force: true });
});
