import { ContractBookingService } from './contract-booking.service';
import { BookingPricingService } from '../bookings/booking-pricing.service';
import type { Contract } from './entities/contract.entity';
import type { ContractRateSnapshot } from './entities/contract-rate-snapshot.entity';

const contract = (over: Partial<Contract>): Contract =>
  ({ tradeDirection: 'IMPORT', paymentCurrency: 'USD', ...over }) as Contract;

/** The private resolver, reached without standing up the whole Nest graph. */
const resolveCurrency = (c: Contract, requested?: string | null): string =>
  (
    ContractBookingService.prototype as unknown as {
      resolveShipmentCurrency: (c: Contract, r?: string | null) => string;
    }
  ).resolveShipmentCurrency(c, requested);

const snapshot = (currency: string, unitPrice: number): ContractRateSnapshot =>
  ({ rateCode: 'CONTAINER_20FT', currency, unitPrice }) as ContractRateSnapshot;

const frozenByCode = (
  snap: ContractRateSnapshot | null,
  bookingCurrency: string,
  usdToEtb: number,
): ContractRateSnapshot | null =>
  (
    BookingPricingService.prototype as unknown as {
      frozenRateByCode: (
        m: Map<string, ContractRateSnapshot> | null,
        code: string,
        bookingCurrency: string,
        usdToEtb: number,
      ) => ContractRateSnapshot | null;
    }
  ).frozenRateByCode(
    snap ? new Map([['CONTAINER_20FT', snap]]) : null,
    'CONTAINER_20FT',
    bookingCurrency,
    usdToEtb,
  );

describe('per-shipment billing currency', () => {
  it('takes the customer choice over the contract', () => {
    expect(resolveCurrency(contract({}), 'ETB')).toBe('ETB');
    expect(resolveCurrency(contract({}), 'USD')).toBe('USD');
  });

  it('falls back to the contract currency when none is chosen', () => {
    // Grandfathered ETB contract with no explicit choice.
    expect(resolveCurrency(contract({ paymentCurrency: 'ETB' }))).toBe('ETB');
    expect(resolveCurrency(contract({}), '   ')).toBe('USD');
  });

  it('forces ETB on intercity whatever was requested', () => {
    const domestic = contract({ tradeDirection: 'DOMESTIC' });
    expect(resolveCurrency(domestic, 'USD')).toBe('ETB');
    expect(resolveCurrency(domestic)).toBe('ETB');
  });
});

describe('frozen contract rate in the booking currency', () => {
  it('converts a USD snapshot for an ETB booking instead of dropping it', () => {
    // The old behaviour returned null here, which silently re-priced the
    // booking at live rates and lost the agreed contract price.
    expect(frozenByCode(snapshot('USD', 400), 'ETB', 150)?.unitPrice).toBe(60_000);
  });

  it('converts a grandfathered ETB snapshot back for a USD booking', () => {
    expect(frozenByCode(snapshot('ETB', 60_000), 'USD', 150)?.unitPrice).toBe(400);
  });

  it('passes a matching-currency snapshot through untouched', () => {
    const snap = snapshot('USD', 400);
    expect(frozenByCode(snap, 'USD', 1)).toBe(snap);
  });

  it('refuses to price off an unusable exchange rate', () => {
    // Converting with 0 would zero the whole line.
    expect(frozenByCode(snapshot('USD', 400), 'ETB', 0)).toBeNull();
    expect(frozenByCode(snapshot('USD', 400), 'ETB', Number.NaN)).toBeNull();
  });

  it('returns null when there is no snapshot', () => {
    expect(frozenByCode(null, 'ETB', 150)).toBeNull();
  });
});
