/**
 * Currency / FX suite (matrix Suite C). Exercises CurrencyService directly.
 *   C2 ✅ FIXED (was 🔴): getExchangeRate() used to silently return 1.0 on a missing rate —
 *         now fails closed with the same BadRequestException getRateOrThrow() always threw
 *         (see the "H-2: fail closed" comment in currency.service.ts). C2/C2b below were
 *         found still asserting the OLD buggy behavior (`resolves.toBe(1.0)`) and were
 *         themselves failing as a result — updated to assert the current, correct behavior.
 *         Do not revert getExchangeRate to silently return 1.0 to make an old version of
 *         this test pass; that would reintroduce a real underpricing bug.
 *   C3 🔴 a future-dated rate is applied immediately (no `effectiveDate <= now` filter).
 *   C5 🔴 conversion routines disagree on units: displayMinorToChargeMajor / convertMinorToChargeMajor
 *         return MAJOR units, convertEtbMinorToChargeMinor returns MINOR — a 100x unit landmine both
 *         written into fields named `amountMinor` at their call sites.
 */
import { CurrencyService } from "../src/modules/currency/currency.service";
import { createServiceHarness, ServiceHarness } from "./setup/slim-app";
import { resetAndSeedCore, USD_TO_ETB } from "./fixtures/seed-core";

describe("Pricing — CurrencyService (Suite C)", () => {
  let harness: ServiceHarness;
  let currency: CurrencyService;

  beforeAll(async () => {
    harness = await createServiceHarness();
    currency = harness.moduleRef.get(CurrencyService);
  });
  afterAll(async () => {
    await harness?.close();
  });
  beforeEach(async () => {
    await resetAndSeedCore(harness.prisma);
  });

  it("C2 ✅ getExchangeRate now fails closed on a missing DIRECT rate, same as getRateOrThrow (no more silent 1.0)", async () => {
    // Remove only the DIRECT USD→ETB row; the inverse ETB→USD (0.01) from the fixture stays.
    // getExchangeRate has no inverse-rate fallback at all (unlike getRateOrThrow, which does)
    // — so it correctly throws here even though a usable inverse rate technically exists.
    await harness.prisma.currencyExchangeRate.deleteMany({
      where: { fromCurrency: "USD", toCurrency: "ETB" },
    });

    await expect(currency.getExchangeRate("USD" as any, "ETB" as any)).rejects.toThrow(/No exchange rate configured/i);

    // getRateOrThrow DOES fall back to the inverse → 1 / 0.01 = 100 (correct) — this divergence
    // (one method has an inverse fallback, the other doesn't) is a separate, real inconsistency
    // from the old "silent 1.0" bug; flagging it, not fixing it here.
    await expect(
      currency.getRateOrThrow("USD" as any, "ETB" as any),
    ).resolves.toBe(USD_TO_ETB);
  });

  it("C2b ✅ truly-missing pair: both getExchangeRate and getRateOrThrow fail closed", async () => {
    await harness.prisma.currencyExchangeRate.deleteMany({
      where: {
        OR: [
          { fromCurrency: "USD", toCurrency: "ETB" },
          { fromCurrency: "ETB", toCurrency: "USD" },
        ],
      },
    });
    await expect(currency.getExchangeRate("USD" as any, "ETB" as any)).rejects.toThrow(/No exchange rate configured/i);
    await expect(
      currency.getRateOrThrow("USD" as any, "ETB" as any),
    ).rejects.toThrow(/No exchange rate/i);
  });

  it("C3 🔴 a future-dated rate is used right now (no effective-date gate)", async () => {
    const future = new Date(Date.now() + 365 * 24 * 3600 * 1000);
    await harness.prisma.currencyExchangeRate.create({
      data: { fromCurrency: "ETB", toCurrency: "USD", rate: 999, effectiveDate: future },
    });

    // Correct behavior: ignore not-yet-effective rates. Actual: latest-by-date wins immediately.
    const rate = await currency.getExchangeRate("ETB" as any, "USD" as any);
    expect(rate).toBe(999);
  });

  it("C5 🔴 conversion routines return different UNITS for the same money (100x apart)", async () => {
    // 100000 ETB minor = 1000.00 ETB. With ETB→USD = 1/100:
    const asMajor = await currency.convertMinorToChargeMajor(100000, "ETB", "USD"); // → 10.00 (major)
    const asMinor = await currency.convertEtbMinorToChargeMinor(100000, "USD"); // → 1000 (minor)

    expect(asMajor).toBeCloseTo(1000 / USD_TO_ETB, 2); // 10.00
    expect(asMinor).toBe(Math.round((100000 * 1) / USD_TO_ETB)); // 1000
    // Same amount, but the two results differ by 100x — and both feed fields named `amountMinor`.
    expect(asMinor).toBe(asMajor * 100);
  });
});
