import { WarehouseFeeService } from './warehouse-fee.service';
import { WarehouseFeeRule } from './entities/warehouse-fee-rule.entity';

// Bulk storage/demurrage used to bill a flat rate per day regardless of cargo
// quantity. It now scales by the cargo type's own unit of measure — tons for
// PER_TON cargo, item count for PER_ITEM cargo (Machinery, Truck, Automobile,
// Livestock…) — read from THIS inventory row, not the whole booking's total.
describe('WarehouseFeeService bulk quantity billing', () => {
  const makeService = () =>
    // compute() only touches its own arguments plus this.convertAmount, which
    // short-circuits when rule.currency === billingCurrency — none of the
    // constructor deps are exercised.
    new WarehouseFeeService({} as any, {} as any, {} as any, {} as any);

  const rule = (overrides: Partial<WarehouseFeeRule> = {}): WarehouseFeeRule =>
    ({
      id: 'rule-1',
      name: 'Bulk storage',
      ruleType: 'STORAGE_FEE',
      freeDays: 0,
      ratePerDay: 10,
      currency: 'USD',
      tiers: [],
      ...overrides,
    }) as WarehouseFeeRule;

  const baseItem = (overrides: Record<string, unknown> = {}) => ({
    arrivedAt: new Date('2026-01-01T00:00:00Z'),
    gateClearedAt: null,
    releaseDate: null,
    freightType: 'BULK',
    tradeDirection: 'IMPORT',
    cargoTypeCode: 'WHEAT',
    containerTypeCode: null,
    vehicleType: null,
    inventoryQuantity: 3,
    inventoryWeight: 25,
    bookingContainerCount: 0,
    cargoUnitOfMeasure: null,
    facilityId: null,
    warehouseId: null,
    yardId: null,
    zoneId: null,
    ...overrides,
  });

  // 5 elapsed days, 0 free days -> 5 chargeable days throughout.
  const now = new Date('2026-01-06T00:00:00Z');

  it('bills PER_TON bulk cargo by this row\'s weight, not a flat day rate', async () => {
    const service = makeService();
    const preview = await (service as any).compute(
      'STORAGE_FEE',
      rule(),
      baseItem({ cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 25 }),
      now,
      'USD',
    );
    expect(preview.unitLabel).toBe('ton');
    expect(preview.containerCount).toBe(25);
    expect(preview.billableUnits).toBe(5 * 25);
    expect(preview.amount).toBe(5 * 25 * 10);
  });

  it('bills PER_ITEM bulk cargo (Machinery/Truck/Automobile/Livestock) by unit count', async () => {
    const service = makeService();
    const preview = await (service as any).compute(
      'STORAGE_FEE',
      rule(),
      baseItem({ cargoUnitOfMeasure: 'PER_ITEM', inventoryQuantity: 3, cargoTypeCode: 'MACHINERY' }),
      now,
      'USD',
    );
    expect(preview.unitLabel).toBe('item');
    expect(preview.containerCount).toBe(3);
    expect(preview.billableUnits).toBe(5 * 3);
    expect(preview.amount).toBe(5 * 3 * 10);
  });

  it('defaults to PER_TON when the cargo type has no unit of measure set', async () => {
    const service = makeService();
    const preview = await (service as any).compute(
      'STORAGE_FEE',
      rule(),
      baseItem({ cargoUnitOfMeasure: null, inventoryWeight: 12 }),
      now,
      'USD',
    );
    expect(preview.unitLabel).toBe('ton');
    expect(preview.containerCount).toBe(12);
  });

  it('charges nothing yet when the row has not been weighed/counted (0 is legitimate, not floored to 1)', async () => {
    const service = makeService();
    const preview = await (service as any).compute(
      'STORAGE_FEE',
      rule(),
      baseItem({ cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 0 }),
      now,
      'USD',
    );
    expect(preview.containerCount).toBe(0);
    expect(preview.billableUnits).toBe(0);
    expect(preview.amount).toBe(0);
  });

  it('leaves CONTAINER freight billing untouched by the new bulk fields', async () => {
    const service = makeService();
    const preview = await (service as any).compute(
      'DEMURRAGE_FEE',
      rule({ ruleType: 'DEMURRAGE_FEE' }),
      baseItem({
        freightType: 'CONTAINER',
        bookingContainerCount: 4,
        cargoUnitOfMeasure: 'PER_ITEM', // must be ignored for container freight
        inventoryWeight: 999,
      }),
      now,
      'USD',
    );
    expect(preview.unitLabel).toBe('container');
    expect(preview.containerCount).toBe(4);
    expect(preview.billableUnits).toBe(5 * 4);
  });

  // Double handling is a flat one-time charge, but previewForInventory() calls
  // it once per warehouse_inventory ROW. Before this fix it read the whole
  // booking's total on every row, so a booking split across N rows was billed
  // N times against its full quantity. Reading each row's own weight/count
  // fixes that: summing the rows now reproduces the booking total exactly once.
  describe('double handling (row-level, not booking-wide)', () => {
    const doubleHandlingRule = (basis: 'PER_CONTAINER' | 'PER_TON' | 'PER_ITEM') =>
      rule({ ruleType: 'DOUBLE_HANDLING_FEE', basis, ratePerDay: 20 });

    it('bills PER_TON by this row\'s own weight', async () => {
      const service = makeService();
      const preview = await (service as any).compute(
        'DOUBLE_HANDLING_FEE',
        doubleHandlingRule('PER_TON'),
        baseItem({ cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 10 }),
        now,
        'USD',
      );
      expect(preview.unitLabel).toBe('ton');
      expect(preview.billableUnits).toBe(10);
      expect(preview.amount).toBe(10 * 20);
    });

    it('bills PER_ITEM by this row\'s own unit count', async () => {
      const service = makeService();
      const preview = await (service as any).compute(
        'DOUBLE_HANDLING_FEE',
        doubleHandlingRule('PER_ITEM'),
        baseItem({ cargoUnitOfMeasure: 'PER_ITEM', inventoryQuantity: 2, cargoTypeCode: 'TRUCK' }),
        now,
        'USD',
      );
      expect(preview.unitLabel).toBe('item');
      expect(preview.billableUnits).toBe(2);
      expect(preview.amount).toBe(2 * 20);
    });

    it('two rows of one booking sum to the booking total exactly once (no N-times overcount)', async () => {
      const service = makeService();
      const ruleDef = doubleHandlingRule('PER_TON');
      const rowA = await (service as any).compute(
        'DOUBLE_HANDLING_FEE',
        ruleDef,
        baseItem({ cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 6 }),
        now,
        'USD',
      );
      const rowB = await (service as any).compute(
        'DOUBLE_HANDLING_FEE',
        ruleDef,
        baseItem({ cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 4 }),
        now,
        'USD',
      );
      // Booking total is 10 tons across the two rows — billed once in total,
      // not 10 tons charged against EACH row (which the old booking-wide read did).
      expect(rowA.amount + rowB.amount).toBe(10 * 20);
    });

    it('no charge for export/domestic regardless of basis', async () => {
      const service = makeService();
      const preview = await (service as any).compute(
        'DOUBLE_HANDLING_FEE',
        doubleHandlingRule('PER_TON'),
        baseItem({ tradeDirection: 'EXPORT', cargoUnitOfMeasure: 'PER_TON', inventoryWeight: 10 }),
        now,
        'USD',
      );
      expect(preview.amount).toBe(0);
    });
  });
});
