import { describe, expect, it } from 'vitest';
import {
  getDeferredRestartAction,
  getReconnectScheduleDecision,
  getReconnectSkipReason,
  isLifecycleSuperseded,
  nextLifecycleEpoch,
  shouldDeferRestart,
} from '@electron/gateway/process-policy';

describe('gateway process policy helpers', () => {
  describe('lifecycle epoch helpers', () => {
    it('increments lifecycle epoch by one', () => {
      expect(nextLifecycleEpoch(0)).toBe(1);
      expect(nextLifecycleEpoch(5)).toBe(6);
    });

    it('detects superseded lifecycle epochs', () => {
      expect(isLifecycleSuperseded(3, 4)).toBe(true);
      expect(isLifecycleSuperseded(8, 8)).toBe(false);
    });
  });

  describe('getReconnectSkipReason', () => {
    it('skips reconnect when auto-reconnect is disabled', () => {
      expect(
        getReconnectSkipReason({
          scheduledEpoch: 10,
          currentEpoch: 10,
          shouldReconnect: false,
        })
      ).toBe('auto-reconnect disabled');
    });

    it('skips stale reconnect callbacks when lifecycle epoch changed', () => {
      expect(
        getReconnectSkipReason({
          scheduledEpoch: 11,
          currentEpoch: 12,
          shouldReconnect: true,
        })
      ).toContain('stale reconnect callback');
    });

    it('allows reconnect when callback is current and reconnect enabled', () => {
      expect(
        getReconnectSkipReason({
          scheduledEpoch: 7,
          currentEpoch: 7,
          shouldReconnect: true,
        })
      ).toBeNull();
    });
  });

  describe('restart deferral policy', () => {
    it('defers restart while startup or reconnect is in progress', () => {
      expect(shouldDeferRestart({ state: 'starting', startLock: false })).toBe(true);
      expect(shouldDeferRestart({ state: 'reconnecting', startLock: false })).toBe(true);
      expect(shouldDeferRestart({ state: 'running', startLock: true })).toBe(true);
    });

    it('does not defer restart for stable states when no start lock', () => {
      expect(shouldDeferRestart({ state: 'running', startLock: false })).toBe(false);
      expect(shouldDeferRestart({ state: 'stopped', startLock: false })).toBe(false);
      expect(shouldDeferRestart({ state: 'error', startLock: false })).toBe(false);
    });

    it('executes deferred restart even after lifecycle recovers to running', () => {
      expect(
        getDeferredRestartAction({
          hasPendingRestart: true,
          state: 'running',
          startLock: false,
          shouldReconnect: true,
        })
      ).toBe('execute');
    });

    it('waits deferred restart while lifecycle is still busy', () => {
      expect(
        getDeferredRestartAction({
          hasPendingRestart: true,
          state: 'starting',
          startLock: false,
          shouldReconnect: true,
        })
      ).toBe('wait');
    });

    it('executes deferred restart when manager is idle and not running', () => {
      expect(
        getDeferredRestartAction({
          hasPendingRestart: true,
          state: 'error',
          startLock: false,
          shouldReconnect: true,
        })
      ).toBe('execute');
    });

    it('drops deferred restart when reconnect is disabled', () => {
      expect(
        getDeferredRestartAction({
          hasPendingRestart: true,
          state: 'stopped',
          startLock: false,
          shouldReconnect: false,
        })
      ).toBe('drop');
    });
  });

  describe('getReconnectScheduleDecision', () => {
    const baseContext = {
      shouldReconnect: true,
      hasReconnectTimer: false,
      reconnectAttempts: 0,
      maxAttempts: 10,
      baseDelay: 1000,
      maxDelay: 30000,
    };

    it('skips reconnect when shouldReconnect is false (intentional stop)', () => {
      const decision = getReconnectScheduleDecision({
        ...baseContext,
        shouldReconnect: false,
      });
      expect(decision).toEqual({ action: 'skip', reason: 'auto-reconnect disabled' });
    });

    it('returns already-scheduled when a reconnect timer exists (prevents double-scheduling)', () => {
      const decision = getReconnectScheduleDecision({
        ...baseContext,
        hasReconnectTimer: true,
      });
      expect(decision).toEqual({ action: 'already-scheduled' });
    });

    it('fails when max reconnect attempts are exhausted', () => {
      const decision = getReconnectScheduleDecision({
        ...baseContext,
        reconnectAttempts: 10,
        maxAttempts: 10,
      });
      expect(decision).toEqual({ action: 'fail', attempts: 10, maxAttempts: 10 });
    });

    it('schedules reconnect with exponential backoff delay', () => {
      const decision = getReconnectScheduleDecision({
        ...baseContext,
        reconnectAttempts: 0,
      });
      expect(decision).toEqual({
        action: 'schedule',
        nextAttempt: 1,
        maxAttempts: 10,
        delay: 1000,
      });
    });

    it('caps backoff delay at maxDelay', () => {
      const decision = getReconnectScheduleDecision({
        ...baseContext,
        reconnectAttempts: 8,
        maxDelay: 30000,
      });
      expect(decision).toMatchObject({ action: 'schedule' });
      if (decision.action === 'schedule') {
        expect(decision.delay).toBeLessThanOrEqual(30000);
      }
    });
  });
});