import { describe, it, expect } from 'vitest';
import { addJitter, reconnectDecision, DEFAULT_RECONNECT_POLICY } from '../fleet-connector.js';

describe('addJitter', () => {
  it('returns a value >= the input delay', () => {
    const jittered = addJitter(1000);
    expect(jittered).toBeGreaterThanOrEqual(1000);
  });

  it('returns a value <= 1.5x the input delay', () => {
    const jittered = addJitter(1000);
    expect(jittered).toBeLessThanOrEqual(1500);
  });

  it('0ms delay stays 0ms', () => {
    expect(addJitter(0)).toBe(0);
  });

  it('returns different values on repeated calls (stochastic)', () => {
    const results = new Set<number>();
    for (let i = 0; i < 20; i++) {
      results.add(addJitter(1000));
    }
    expect(results.size).toBeGreaterThan(1);
  });
});

describe('reconnectDecision', () => {
  it('first failure returns baseMs with jitter', () => {
    const decision = reconnectDecision(1);
    expect(decision.action).toBe('reconnect');
    expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs);
    expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 1.5);
  });

  it('exponential growth: failure 2 returns 2x baseMs with jitter', () => {
    const decision = reconnectDecision(2);
    expect(decision.action).toBe('reconnect');
    expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 2);
    expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 3);
  });

  it('exponential growth: failure 3 returns 4x baseMs with jitter', () => {
    const decision = reconnectDecision(3);
    expect(decision.action).toBe('reconnect');
    expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 4);
    expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.baseMs * 6);
  });

  it('capped at maxMs with jitter', () => {
    const decision = reconnectDecision(6);
    expect(decision.action).toBe('reconnect');
    expect(decision.delayMs).toBeGreaterThanOrEqual(DEFAULT_RECONNECT_POLICY.maxMs);
    expect(decision.delayMs).toBeLessThanOrEqual(DEFAULT_RECONNECT_POLICY.maxMs * 1.5);
  });

  it('gives up after maxAttempts', () => {
    const decision = reconnectDecision(DEFAULT_RECONNECT_POLICY.maxAttempts + 1);
    expect(decision).toEqual({ action: 'give-up' });
  });

  it('custom policy works with jitter', () => {
    const policy = { baseMs: 500, maxMs: 5000, maxAttempts: 3 };
    const d1 = reconnectDecision(1, policy);
    expect(d1.action).toBe('reconnect');
    expect(d1.delayMs).toBeGreaterThanOrEqual(500);
    expect(d1.delayMs).toBeLessThanOrEqual(750);

    const d2 = reconnectDecision(2, policy);
    expect(d2.action).toBe('reconnect');
    expect(d2.delayMs).toBeGreaterThanOrEqual(1000);
    expect(d2.delayMs).toBeLessThanOrEqual(1500);

    const d3 = reconnectDecision(3, policy);
    expect(d3.action).toBe('reconnect');
    expect(d3.delayMs).toBeGreaterThanOrEqual(2000);
    expect(d3.delayMs).toBeLessThanOrEqual(3000);

    const d4 = reconnectDecision(4, policy);
    expect(d4).toEqual({ action: 'give-up' });
  });
});
