/**
 * Tests for the margin arithmetic.
 *
 * This is the most load-bearing pure code in the project: every dashboard
 * figure, every idle-capacity alert and every answer an agent gives resolves
 * through these functions. An error here is an error everywhere, and it would
 * be a quiet one — wrong numbers still look like numbers.
 *
 * The cases below are chosen to pin the *decisions*, not merely the
 * implementation. Several would pass under a naive version that is wrong in a
 * way that flatters the business, which is precisely why they exist.
 */
import { strict as assert } from 'node:assert';
import { describe, it } from 'node:test';
import {
  aggregateMargin,
  breakEvenPricePerGpuHourCents,
  computeMargin,
  formatCents,
} from '../src/margin';

describe('computeMargin', () => {
  it('charges cost against the FULL commitment, not only the hours sold', () => {
    // The decision the whole product rests on. A naive implementation charges
    // only the allocated share and reports this block as profitable, when in
    // fact it loses money: the unsold hours were already paid for.
    const result = computeMargin(
      { gpuHours: 1000, costPerGpuHourCents: 100 },
      [{ gpuHours: 500, pricePerGpuHourCents: 150 }],
    );

    assert.equal(result.costCents, 100_000, 'cost must cover all 1000 hours');
    assert.equal(result.revenueCents, 75_000);
    assert.equal(result.grossMarginCents, -25_000, 'this block is underwater');

    // Under the naive treatment: cost 500×100 = 50,000 against revenue 75,000,
    // reporting +25,000 — a sign error on the thing that matters most.
    assert.ok(result.grossMarginCents < 0);
  });

  it('reports utilisation and idle hours', () => {
    const r = computeMargin({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 700, pricePerGpuHourCents: 200 },
    ]);
    assert.equal(r.allocatedGpuHours, 700);
    assert.equal(r.idleGpuHours, 300);
    assert.equal(r.utilisation, 0.7);
  });

  it('sums several allocations against one commitment', () => {
    const r = computeMargin({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 400, pricePerGpuHourCents: 200 },
      { gpuHours: 300, pricePerGpuHourCents: 250 },
    ]);
    assert.equal(r.allocatedGpuHours, 700);
    assert.equal(r.revenueCents, 400 * 200 + 300 * 250);
  });

  it('counts internal consumption as cost with no revenue', () => {
    // Research burn is a zero-price allocation. Leaving it out would overstate
    // available capacity; pricing it at anything but zero would invent revenue.
    const r = computeMargin({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 600, pricePerGpuHourCents: 200 },
      { gpuHours: 200, pricePerGpuHourCents: 0 },
    ]);
    assert.equal(r.allocatedGpuHours, 800);
    assert.equal(r.revenueCents, 120_000, 'the internal 200 hours earn nothing');
    assert.equal(r.idleGpuHours, 200);
  });

  it('clamps idle at zero when capacity is deliberately oversubscribed', () => {
    // Selling beyond 100% is a real policy, not a data-entry error. Negative
    // idle hours would be nonsense to display.
    const r = computeMargin({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 1200, pricePerGpuHourCents: 150 },
    ]);
    assert.equal(r.idleGpuHours, 0);
    assert.equal(r.utilisation, 1.2, 'utilisation itself is not clamped — it is the signal');
  });

  it('returns null rather than dividing by zero on an empty book', () => {
    const r = computeMargin({ gpuHours: 1000, costPerGpuHourCents: 100 }, []);
    assert.equal(r.revenueCents, 0);
    assert.equal(r.grossMarginCents, -100_000);
    assert.equal(r.grossMarginPct, null, 'no revenue means no percentage, not Infinity');
    assert.equal(r.marginPerAllocatedGpuHourCents, null);
  });

  it('handles a zero-hour commitment without producing NaN', () => {
    const r = computeMargin({ gpuHours: 0, costPerGpuHourCents: 100 }, []);
    assert.equal(r.utilisation, 0);
    assert.ok(!Number.isNaN(r.utilisation));
  });

  it('keeps money in whole cents', () => {
    // Fractional hours are normal; fractional cents are not. A dashboard that
    // renders 1234.9999999 has lost the plot.
    const r = computeMargin({ gpuHours: 0.1, costPerGpuHourCents: 3 }, [
      { gpuHours: 0.1, pricePerGpuHourCents: 7 },
    ]);
    assert.ok(Number.isInteger(r.costCents));
    assert.ok(Number.isInteger(r.revenueCents));
  });
});

describe('breakEvenPricePerGpuHourCents', () => {
  it('prices the REMAINING hours, not the whole block', () => {
    // Half a 1000-hour block at 100c cost is sold at 120c: 60,000 of 100,000
    // recovered, 40,000 outstanding across 500 remaining hours = 80c.
    const price = breakEvenPricePerGpuHourCents({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 500, pricePerGpuHourCents: 120 },
    ]);
    assert.equal(price, 80);
  });

  it('falls as more of the block sells', () => {
    const commitment = { gpuHours: 1000, costPerGpuHourCents: 100 };
    const early = breakEvenPricePerGpuHourCents(commitment, [
      { gpuHours: 200, pricePerGpuHourCents: 120 },
    ])!;
    const later = breakEvenPricePerGpuHourCents(commitment, [
      { gpuHours: 800, pricePerGpuHourCents: 120 },
    ])!;
    assert.ok(later < early, 'a mostly-sold block is cheaper to break even on');
  });

  it('returns zero once cost is covered — further sales are pure upside', () => {
    const price = breakEvenPricePerGpuHourCents({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 500, pricePerGpuHourCents: 300 },
    ]);
    // Not a negative price, which would be meaningless to show a seller.
    assert.equal(price, 0);
  });

  it('returns null when nothing is left to sell', () => {
    const price = breakEvenPricePerGpuHourCents({ gpuHours: 1000, costPerGpuHourCents: 100 }, [
      { gpuHours: 1000, pricePerGpuHourCents: 150 },
    ]);
    assert.equal(price, null, 'no remaining hours means no break-even price, not Infinity');
  });
});

describe('aggregateMargin', () => {
  it('sums cents rather than averaging percentages', () => {
    // A tiny block at a wonderful margin next to a huge one at a terrible
    // margin. Averaging the two percentages would report roughly break-even;
    // the truth is a large loss dominated by the big block.
    const result = aggregateMargin([
      {
        commitment: { gpuHours: 10, costPerGpuHourCents: 100 },
        allocations: [{ gpuHours: 10, pricePerGpuHourCents: 1000 }],
      },
      {
        commitment: { gpuHours: 10_000, costPerGpuHourCents: 100 },
        allocations: [{ gpuHours: 5_000, pricePerGpuHourCents: 110 }],
      },
    ]);

    assert.equal(result.costCents, 10 * 100 + 10_000 * 100);
    assert.equal(result.revenueCents, 10 * 1000 + 5_000 * 110);
    assert.ok(result.grossMarginCents < 0, 'the big block dominates');

    // The naive mean of +90% and -45% is about +22%, which is the wrong sign.
    assert.ok(result.grossMarginPct! < 0);
  });

  it('is empty-safe', () => {
    const r = aggregateMargin([]);
    assert.equal(r.costCents, 0);
    assert.equal(r.grossMarginPct, null);
    assert.equal(r.utilisation, 0);
  });
});

describe('formatCents', () => {
  it('renders whole currency from integer cents', () => {
    assert.equal(formatCents(123_456), '$1,234.56');
    assert.equal(formatCents(0), '$0.00');
  });

  it('renders negatives, which is the case that matters', () => {
    assert.ok(formatCents(-5000).includes('50'));
  });
});
