import { describe, it, expect, beforeEach } from 'vitest';
import { ActionQueue } from '../action-queue.js';
import type { ActionQueueDeps, QueuedAction } from '../action-queue.js';

describe('ActionQueue', () => {
  let queue: ActionQueue;
  let deps: ActionQueueDeps;

  beforeEach(() => {
    queue = new ActionQueue();
    deps = {
      baseUrl: 'http://test-host:8401',
      isLivenessUp: () => true,
      isInflightRequests: () => 0,
      log: {
        error: () => {},
        warn: () => {},
        info: () => {},
        debug: () => {},
        trace: () => {},
        fatal: () => {},
        child: () => deps.log,
      } as any,
    };
    queue.registerHost('host1', deps);
  });

  describe('submit', () => {
    it('rejects submission when host is down', () => {
      const downQueue = new ActionQueue();
      const downDeps: ActionQueueDeps = {
        ...deps,
        isLivenessUp: () => false,
      };
      downQueue.registerHost('down-host', downDeps);

      const result = downQueue.submit({
        actionId: 'a1',
        type: 'warm',
        providerId: 'down-host',
        confirmed: false,
        createdAt: new Date(),
      });

      expect(result.ok).toBe(false);
      if (!result.ok) {
        expect(result.error).toBe('host offline');
      }
    });

    it('rejects submission when queue is full (depth 4)', () => {
      // Fill the queue to capacity
      for (let i = 0; i < 4; i++) {
        const result = queue.submit({
          actionId: `fill-${i}`,
          type: 'warm',
          providerId: 'host1',
          model: 'model1',
          confirmed: false,
          createdAt: new Date(),
        });
        expect(result.ok).toBe(true);
      }

      // 5th submission should be rejected
      const result = queue.submit({
        actionId: 'overflow',
        type: 'warm',
        providerId: 'host1',
        model: 'model1',
        confirmed: false,
        createdAt: new Date(),
      });

      expect(result.ok).toBe(false);
      if (!result.ok) {
        expect(result.error).toContain('queue full');
        expect(result.pending).toHaveLength(4);
      }
    });

    it('returns 409 with requiresConfirmation for unload during inflight', () => {
      const inflightDeps: ActionQueueDeps = {
        ...deps,
        isInflightRequests: () => 5,
      };
      const inflightQueue = new ActionQueue();
      inflightQueue.registerHost('busy-host', inflightDeps);

      const result = inflightQueue.submit({
        actionId: 'unload-1',
        type: 'unload',
        providerId: 'busy-host',
        confirmed: false,
        createdAt: new Date(),
      });

      expect(result.ok).toBe(false);
      if (!result.ok) {
        expect(result.error).toBe('bench in progress');
        expect(result.requiresConfirmation).toBe(true);
      }
    });

    it('allows confirmed unload during inflight', () => {
      const inflightDeps: ActionQueueDeps = {
        ...deps,
        isInflightRequests: () => 5,
      };
      const inflightQueue = new ActionQueue();
      inflightQueue.registerHost('busy-host', inflightDeps);

      const result = inflightQueue.submit({
        actionId: 'unload-confirmed',
        type: 'unload',
        providerId: 'busy-host',
        confirmed: true,
        createdAt: new Date(),
      });

      expect(result.ok).toBe(true);
    });

    it('accepts a warm action when queue has capacity', () => {
      const result = queue.submit({
        actionId: 'warm-1',
        type: 'warm',
        providerId: 'host1',
        model: 'llama3',
        confirmed: false,
        createdAt: new Date(),
      });

      expect(result.ok).toBe(true);
    });
  });

  describe('getState', () => {
    it('returns null for unknown host', () => {
      expect(queue.getState('unknown')).toBeNull();
    });

    it('returns state with entries after submission', () => {
      queue.submit({
        actionId: 'test-1',
        type: 'warm',
        providerId: 'host1',
        model: 'llama3',
        confirmed: false,
        createdAt: new Date(),
      });

      const state = queue.getState('host1');
      expect(state).not.toBeNull();
      expect(state!.queue.length).toBe(1);
      expect(state!.queue[0].action.actionId).toBe('test-1');
      // Status transitions to 'running' as processNext kicks off asynchronously
      expect(['pending', 'running']).toContain(state!.queue[0].status);
    });
  });

  describe('processNext (stale action skip)', () => {
    it('skips an action when host goes down during processing', async () => {
      let livenessUp = true;
      const dynamicDeps: ActionQueueDeps = {
        ...deps,
        isLivenessUp: () => livenessUp,
      };
      const dynamicQueue = new ActionQueue();
      dynamicQueue.registerHost('flaky-host', dynamicDeps);

      // Submit an action
      dynamicQueue.submit({
        actionId: 'stale-1',
        type: 'warm',
        providerId: 'flaky-host',
        model: 'llama3',
        confirmed: false,
        createdAt: new Date(),
      });

      // Turn host down before processing
      livenessUp = false;

      // The queue processor will skip the action
      // We can't easily test the async processNext directly, but we can verify
      // the state reflects the skip logic by checking the queue state
      const state = dynamicQueue.getState('flaky-host');
      expect(state).not.toBeNull();
      expect(state!.queue.length).toBe(1);
      // The entry is still pending; processNext would mark it skipped
    });
  });
});
