import { describe, expect, it } from 'vitest';
import type { SimSnapshot } from '../contracts';
import { formatReserve, powerState, TIGHT_AT } from './power';

type BW = SimSnapshot['bandwidth'];
const bw = (over: Partial<BW> = {}): BW =>
  ({ gen: 100, draw: 50, stored: 0, brownout: false, ...over });

describe('powerState', () => {
  it('is ok with headroom', () => {
    expect(powerState(bw()).state).toBe('ok');
  });

  it('goes tight at 85% of generation, before anything is wrong', () => {
    expect(powerState(bw({ draw: 84 })).state).toBe('ok');
    expect(powerState(bw({ draw: TIGHT_AT * 100 })).state).toBe('tight');
    expect(powerState(bw({ draw: 99 })).state).toBe('tight');
  });

  // ---- the round-3 bug, pinned ------------------------------------------------
  it('a covered deficit is ON RESERVE, not a brownout', () => {
    // draw exceeds gen, tanks have charge, and the sim says no brownout.
    // The old code keyed on `out || ratio > 1` and screamed BROWNOUT at this.
    const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false }));
    expect(p.state).toBe('reserve');
    expect(p.state).not.toBe('brownout');
  });

  it('reports seconds of reserve left, because stored is bandwidth-SECONDS', () => {
    // 1000 bandwidth-seconds stored, bleeding 34/sec => ~29s
    const p = powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: false }));
    expect(p.reserveSeconds).toBeCloseTo(1000 / 34, 5);
  });

  it('only the sim gets to say BROWNOUT', () => {
    // Same covered-deficit numbers, but the sim has raised the flag: believe the flag.
    expect(powerState(bw({ gen: 100, draw: 134, stored: 1000, brownout: true })).state)
      .toBe('brownout');
    // ...and a brownout at nominal draw is still a brownout.
    expect(powerState(bw({ gen: 100, draw: 1, brownout: true })).state).toBe('brownout');
  });

  it('treats an exhausted reserve honestly rather than rounding it up', () => {
    const p = powerState(bw({ gen: 10, draw: 20, stored: 0, brownout: false }));
    expect(p.state).toBe('reserve');
    expect(p.reserveSeconds).toBe(0); // about to become the sim's problem
  });

  it('handles drawing with no generation at all', () => {
    const p = powerState(bw({ gen: 0, draw: 4, stored: 40, brownout: false }));
    expect(p.state).toBe('reserve');
    expect(p.ratio).toBe(Infinity);
    expect(p.reserveSeconds).toBe(10);
  });

  it('is ok on an empty floor — no generation, no draw', () => {
    const p = powerState(bw({ gen: 0, draw: 0 }));
    expect(p.state).toBe('ok');
    expect(p.ratio).toBe(0);
  });

  it('exactly break-even is not a deficit', () => {
    expect(powerState(bw({ gen: 100, draw: 100, stored: 5 })).state).toBe('tight');
  });
});

describe('formatReserve', () => {
  it('floors, because a countdown that rounds up lies at the end', () => {
    expect(formatReserve(29.9)).toBe('29s');
    expect(formatReserve(0.4)).toBe('0s');
  });

  it('caps absurd numbers instead of printing a wall of digits', () => {
    expect(formatReserve(100000)).toBe('999s');
    expect(formatReserve(Infinity)).toBe('∞');
  });
});
