import { describe, it, expect, beforeEach, vi } from 'vitest';
import { PriorityQueue } from './index.js';

describe('PriorityQueue', () => {
  let queue: PriorityQueue;

  beforeEach(() => {
    queue = new PriorityQueue();
  });

  describe('add', () => {
    it('should add an item with correct priority based on type', async () => {
      const item = await queue.add('stars_payment', 'char-1', { amount: 100 });
      expect(item.type).toBe('stars_payment');
      expect(item.priority).toBe(1);
      expect(item.status).toBe('pending');
      expect(item.characterId).toBe('char-1');
      expect(item.retryCount).toBe(0);
      expect(item.maxRetries).toBe(3);
    });

    it('should assign correct priorities for all types', async () => {
      const stars = await queue.add('stars_payment', 'c1', {});
      const chat = await queue.add('chat_response', 'c1', {});
      const image = await queue.add('image_generation', 'c1', {});
      const quality = await queue.add('quality_check', 'c1', {});
      const publish = await queue.add('channel_publish', 'c1', {});
      const translate = await queue.add('translation', 'c1', {});

      expect(stars.priority).toBe(1);
      expect(chat.priority).toBe(2);
      expect(image.priority).toBe(3);
      expect(quality.priority).toBe(4);
      expect(publish.priority).toBe(5);
      expect(translate.priority).toBe(5);
    });

    it('should use custom maxRetries when provided', async () => {
      const item = await queue.add('chat_response', 'c1', {}, 5);
      expect(item.maxRetries).toBe(5);
    });
  });

  describe('dequeue', () => {
    it('should return undefined when queue is empty', () => {
      expect(queue.dequeue()).toBeUndefined();
    });

    it('should dequeue highest priority item first', async () => {
      await queue.add('channel_publish', 'c1', { order: 1 });
      await queue.add('stars_payment', 'c1', { order: 2 });
      await queue.add('chat_response', 'c1', { order: 3 });

      const item = queue.dequeue();
      expect(item?.type).toBe('stars_payment');
      expect(item?.status).toBe('processing');
    });

    it('should follow FIFO for same priority items', async () => {
      const first = await queue.add('channel_publish', 'c1', { order: 'first' });
      const second = await queue.add('translation', 'c1', { order: 'second' });

      const dequeued = queue.dequeue();
      expect(dequeued?.id).toBe(first.id);
    });

    it('should skip non-pending items', async () => {
      const item1 = await queue.add('stars_payment', 'c1', {});
      await queue.add('chat_response', 'c1', {});

      // Dequeue first item (marks it as processing)
      queue.dequeue();
      // Next dequeue should get the chat_response
      const next = queue.dequeue();
      expect(next?.type).toBe('chat_response');
    });
  });

  describe('updateStatus', () => {
    it('should update item status', async () => {
      const item = await queue.add('chat_response', 'c1', {});
      const updated = await queue.updateStatus(item.id, 'completed');
      expect(updated?.status).toBe('completed');
    });

    it('should set error on failure', async () => {
      const item = await queue.add('chat_response', 'c1', {});
      const updated = await queue.updateStatus(item.id, 'failed', 'API timeout');
      expect(updated?.error).toBe('API timeout');
      expect(updated?.retryCount).toBe(1);
    });

    it('should return null for non-existent item', async () => {
      const result = await queue.updateStatus('non-existent', 'completed');
      expect(result).toBeNull();
    });
  });

  describe('getById', () => {
    it('should find item by id', async () => {
      const item = await queue.add('chat_response', 'c1', { data: 'test' });
      const found = queue.getById(item.id);
      expect(found?.id).toBe(item.id);
    });

    it('should return undefined for non-existent id', () => {
      expect(queue.getById('missing')).toBeUndefined();
    });
  });

  describe('getPending / getFailed / getAll', () => {
    it('should filter items by status', async () => {
      const item1 = await queue.add('chat_response', 'c1', {});
      const item2 = await queue.add('image_generation', 'c1', {});
      await queue.updateStatus(item2.id, 'failed', 'error');

      expect(queue.getPending()).toHaveLength(1);
      expect(queue.getFailed()).toHaveLength(1);
      expect(queue.getAll()).toHaveLength(2);
    });
  });

  describe('getStats', () => {
    it('should return correct counts', async () => {
      await queue.add('chat_response', 'c1', {});
      await queue.add('image_generation', 'c1', {});
      const item3 = await queue.add('quality_check', 'c1', {});
      await queue.updateStatus(item3.id, 'failed', 'err');

      const stats = queue.getStats();
      expect(stats.pending).toBe(2);
      expect(stats.failed).toBe(1);
      expect(stats.processing).toBe(0);
      expect(stats.completed).toBe(0);
    });
  });

  describe('retry', () => {
    it('should re-queue a failed item', async () => {
      const item = await queue.add('chat_response', 'c1', {});
      await queue.updateStatus(item.id, 'failed', 'timeout');

      const result = await queue.retry(item.id);
      expect(result).toBe(true);

      const retried = queue.getById(item.id);
      expect(retried?.status).toBe('pending');
      expect(retried?.error).toBeUndefined();
    });

    it('should refuse retry when maxRetries exceeded', async () => {
      const item = await queue.add('chat_response', 'c1', {}, 1);
      await queue.updateStatus(item.id, 'failed', 'err');

      const result = await queue.retry(item.id);
      expect(result).toBe(false);
    });

    it('should refuse retry for non-failed items', async () => {
      const item = await queue.add('chat_response', 'c1', {});
      const result = await queue.retry(item.id);
      expect(result).toBe(false);
    });
  });

  describe('loadItems', () => {
    it('should load and sort items', () => {
      const items = [
        {
          id: '1', type: 'channel_publish' as const, priority: 5, status: 'pending' as const,
          characterId: 'c1', payload: {}, retryCount: 0, maxRetries: 3,
          createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z',
        },
        {
          id: '2', type: 'stars_payment' as const, priority: 1, status: 'pending' as const,
          characterId: 'c1', payload: {}, retryCount: 0, maxRetries: 3,
          createdAt: '2024-01-01T00:00:01Z', updatedAt: '2024-01-01T00:00:01Z',
        },
      ];

      queue.loadItems(items);
      const dequeued = queue.dequeue();
      expect(dequeued?.id).toBe('2'); // stars_payment has higher priority
    });
  });

  describe('processNext (via processor)', () => {
    it('should auto-process items when processor is set', async () => {
      const processed: string[] = [];
      queue.setProcessor(async (item) => {
        processed.push(item.id);
      });

      const item = await queue.add('chat_response', 'c1', {});

      // Wait for async processing
      await new Promise(resolve => setTimeout(resolve, 50));

      expect(processed).toContain(item.id);
      expect(queue.getById(item.id)?.status).toBe('completed');
    });

    it('should handle processor errors and mark as failed', async () => {
      queue.setProcessor(async () => {
        throw new Error('Processing failed');
      });

      const item = await queue.add('chat_response', 'c1', {}, 0);

      await new Promise(resolve => setTimeout(resolve, 50));

      const updated = queue.getById(item.id);
      expect(updated?.status).toBe('failed');
      expect(updated?.error).toBe('Processing failed');
    });
  });

  describe('persist callback', () => {
    it('should call onPersist when items change', async () => {
      const persistedItems: any[] = [];
      queue.setOnPersist(async (items) => {
        persistedItems.push(items);
      });

      await queue.add('chat_response', 'c1', {});
      expect(persistedItems.length).toBeGreaterThan(0);
    });
  });
});
