import test from "node:test";
import assert from "node:assert/strict";
import { authorize, Permissions, type StaffPrincipal } from "../src/modules/identity/permissions.ts";
import { assertSellerMaySell, createResubmission, requiredChallengeDate, reviewKyc, submitKyc, type KycSubmission } from "../src/modules/kyc/seller-verification.ts";
import { Money } from "../src/shared/money.ts";

const draft = (): KycSubmission => ({ id: "kyc-1", sellerId: "seller-1", sequence: 1, status: "draft", challengeDate: "" });

test("KYC requires front, back and challenge selfie", () => {
  assert.throws(() => submitKyc(draft(), { idFrontObjectKey: "private/a", idBackObjectKey: "private/b", challengeDate: "2026-09-13" }, new Date("2026-09-13T12:00:00Z"), "UTC"), { code: "KYC_EVIDENCE_REQUIRED" });
});

test("KYC challenge date is evaluated in configured timezone", () => {
  const now = new Date("2026-09-13T16:30:00Z");
  assert.equal(requiredChallengeDate(now, "UTC"), "2026-09-13");
  assert.equal(requiredChallengeDate(now, "Asia/Manila"), "2026-09-14");
});

test("stale KYC date is rejected server-side", () => {
  assert.throws(() => submitKyc(draft(), { idFrontObjectKey: "a", idBackObjectKey: "b", selfieObjectKey: "c", challengeDate: "2026-09-12" }, new Date("2026-09-13T12:00:00Z"), "UTC"), { code: "STALE_KYC_CHALLENGE" });
});

test("only approved latest KYC unlocks selling", () => {
  assert.throws(() => assertSellerMaySell(null), { code: "SELLER_KYC_REQUIRED" });
  assert.throws(() => assertSellerMaySell({ ...draft(), status: "pending" }), { code: "SELLER_KYC_REQUIRED" });
  assert.throws(() => assertSellerMaySell({ ...draft(), status: "rejected" }), { code: "SELLER_KYC_REQUIRED" });
  assert.doesNotThrow(() => assertSellerMaySell({ ...draft(), status: "approved" }));
});

test("rejection preserves history and creates a new submission", () => {
  const pending = submitKyc(draft(), { idFrontObjectKey: "a", idBackObjectKey: "b", selfieObjectKey: "c", challengeDate: "2026-09-13" }, new Date("2026-09-13T12:00:00Z"), "UTC");
  const rejected = reviewKyc(pending, "rejected", "admin-1", new Date("2026-09-13T13:00:00Z"), "Image is unreadable");
  const next = createResubmission(rejected, "kyc-2");
  assert.equal(rejected.id, "kyc-1");
  assert.equal(next.id, "kyc-2");
  assert.equal(next.sequence, 2);
  assert.equal(next.status, "draft");
});

test("restricted dashboard staff is denied every sensitive permission", () => {
  const principal: StaffPrincipal = { staffId: "staff-1", active: true, permissions: new Set([Permissions.DashboardView]) };
  assert.doesNotThrow(() => authorize(principal, Permissions.DashboardView));
  for (const permission of Object.values(Permissions).filter((p) => p !== Permissions.DashboardView)) {
    assert.throws(() => authorize(principal, permission), { code: "FORBIDDEN" });
  }
});

test("money uses bigint minor units and prevents currency/underflow errors", () => {
  const exact = Money.ofMinor(10_000n, "php").subtract(Money.ofMinor(10_000n, "PHP"));
  assert.equal(exact.minor, 0n);
  assert.throws(() => Money.ofMinor(100n, "USD").add(Money.ofMinor(1n, "PHP")), { code: "CURRENCY_MISMATCH" });
  assert.throws(() => Money.ofMinor(100n, "USD").subtract(Money.ofMinor(101n, "USD")), { code: "INSUFFICIENT_AMOUNT" });
});
