/**
 * Unit tests for QueueManager
 * 
 * Tests core queue operations, status management, and pub/sub functionality.
 */

import { describe, it, expect, beforeEach, vi } from 'vitest';
import { QueueManager } from '$lib/server/queue/QueueManager';

describe('QueueManager', () => {
  let queueManager: QueueManager;
  
  beforeEach(() => {
    // Create fresh instance for each test
    queueManager = new QueueManager();
  });
  
  describe('enqueue', () => {
    it('should enqueue items with unique IDs', () => {
      const item1 = queueManager.enqueue('https://instagram.com/p/test1');
      const item2 = queueManager.enqueue('https://instagram.com/p/test2');
      
      expect(item1.id).toBeTruthy();
      expect(item2.id).toBeTruthy();
      expect(item1.id).not.toBe(item2.id);
    });
    
    it('should create items with pending status', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      expect(item.status).toBe('pending');
      expect(item.enqueuedAt).toBeTruthy();
      expect(item.logs).toEqual([]);
      expect(item.progressEvents).toEqual([]);
      expect(item.retryCount).toBe(0);
      expect(item.maxRetries).toBe(3);
    });
    
    it('should notify subscribers when enqueueing', () => {
      const callback = vi.fn();
      queueManager.subscribe(callback);
      
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      expect(callback).toHaveBeenCalledWith(
        expect.objectContaining({
          itemId: item.id,
          status: 'pending'
        })
      );
    });
  });
  
  describe('dequeue', () => {
    it('should dequeue oldest pending item first (FIFO)', () => {
      const item1 = queueManager.enqueue('https://instagram.com/p/test1');
      const item2 = queueManager.enqueue('https://instagram.com/p/test2');
      
      const dequeued1 = queueManager.dequeue();
      expect(dequeued1?.id).toBe(item1.id);
      
      const dequeued2 = queueManager.dequeue();
      expect(dequeued2?.id).toBe(item2.id);
    });
    
    it('should return null when queue is empty', () => {
      const item = queueManager.dequeue();
      expect(item).toBeNull();
    });
    
    it('should mark dequeued item as in_progress', () => {
      const enqueuedItem = queueManager.enqueue('https://instagram.com/p/test');
      
      const dequeuedItem = queueManager.dequeue();
      
      expect(dequeuedItem?.status).toBe('in_progress');
      expect(dequeuedItem?.currentPhase).toBe('extraction');
      expect(dequeuedItem?.startedAt).toBeTruthy();
    });
    
    it('should skip non-pending items', () => {
      const item1 = queueManager.enqueue('https://instagram.com/p/test1');
      const item2 = queueManager.enqueue('https://instagram.com/p/test2');
      
      // Dequeue first item
      queueManager.dequeue();
      
      // Second item should be next
      const dequeued = queueManager.dequeue();
      expect(dequeued?.id).toBe(item2.id);
    });
  });
  
  describe('updateStatus', () => {
    it('should update item status', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      queueManager.updateStatus(item.id, 'in_progress', { phase: 'parsing' });
      
      const updated = queueManager.get(item.id);
      expect(updated?.status).toBe('in_progress');
      expect(updated?.currentPhase).toBe('parsing');
    });
    
    it('should set completedAt for terminal statuses', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      queueManager.updateStatus(item.id, 'success');
      
      const updated = queueManager.get(item.id);
      expect(updated?.completedAt).toBeTruthy();
    });
    
    it('should merge additional data into item', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      queueManager.updateStatus(item.id, 'success', {
        recipe: { name: 'Test Recipe' },
        tandoorRecipeId: 123
      });
      
      const updated = queueManager.get(item.id);
      expect(updated?.recipe).toEqual({ name: 'Test Recipe' });
      expect(updated?.tandoorRecipeId).toBe(123);
    });
    
    it('should handle error data', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      const errorData = {
        error: {
          phase: 'extraction' as const,
          message: 'Failed to load page',
          recoverable: true,
          timestamp: new Date().toISOString()
        }
      };
      
      queueManager.updateStatus(item.id, 'unhealthy', errorData);
      
      const updated = queueManager.get(item.id);
      expect(updated?.error).toEqual(errorData.error);
    });
  });
  
  describe('addProgressEvent', () => {
    it('should add progress events to item', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      const event = {
        type: 'status',
        message: 'Extracting...',
        timestamp: new Date().toISOString()
      };
      
      queueManager.addProgressEvent(item.id, event);
      
      const updated = queueManager.get(item.id);
      expect(updated?.progressEvents).toHaveLength(1);
      expect(updated?.progressEvents[0]).toEqual(event);
    });
    
    it('should add event message to logs', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      queueManager.addProgressEvent(item.id, {
        type: 'status',
        message: 'Test message',
        timestamp: new Date().toISOString()
      });
      
      const updated = queueManager.get(item.id);
      expect(updated?.logs).toContain('Test message');
    });
    
    it('should notify subscribers with event data', () => {
      const callback = vi.fn();
      queueManager.subscribe(callback);
      
      const item = queueManager.enqueue('https://instagram.com/p/test');
      callback.mockClear(); // Clear enqueue notification
      
      const event = { type: 'status', message: 'Test', timestamp: new Date().toISOString() };
      queueManager.addProgressEvent(item.id, event);
      
      expect(callback).toHaveBeenCalledWith(
        expect.objectContaining({
          itemId: item.id,
          data: { event }
        })
      );
    });
  });
  
  describe('remove', () => {
    it('should remove items by ID', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      const removed = queueManager.remove(item.id);
      
      expect(removed).toBe(true);
      expect(queueManager.get(item.id)).toBeUndefined();
    });
    
    it('should return false for non-existent items', () => {
      const removed = queueManager.remove('non-existent-id');
      expect(removed).toBe(false);
    });
    
    it('should notify subscribers when removing', () => {
      const callback = vi.fn();
      queueManager.subscribe(callback);
      
      const item = queueManager.enqueue('https://instagram.com/p/test');
      callback.mockClear();
      
      queueManager.remove(item.id);
      
      expect(callback).toHaveBeenCalledWith(
        expect.objectContaining({
          itemId: item.id,
          data: { removed: true }
        })
      );
    });
  });
  
  describe('retry', () => {
    it('should retry failed items', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      queueManager.updateStatus(item.id, 'error');
      
      const retried = queueManager.retry(item.id);
      
      expect(retried).toBe(true);
      
      const updated = queueManager.get(item.id);
      expect(updated?.status).toBe('pending');
      expect(updated?.retryCount).toBe(1);
      expect(updated?.error).toBeUndefined();
      expect(updated?.currentPhase).toBeUndefined();
    });
    
    it('should not retry items in progress', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      queueManager.updateStatus(item.id, 'in_progress');
      
      const retried = queueManager.retry(item.id);
      
      expect(retried).toBe(false);
      expect(queueManager.get(item.id)?.status).toBe('in_progress');
    });
    
    it('should increment retry count', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      queueManager.updateStatus(item.id, 'error');
      
      queueManager.retry(item.id);
      queueManager.retry(item.id);
      
      expect(queueManager.get(item.id)?.retryCount).toBe(2);
    });
  });
  
  describe('getAll', () => {
    it('should return all queue items', () => {
      queueManager.enqueue('https://instagram.com/p/test1');
      queueManager.enqueue('https://instagram.com/p/test2');
      queueManager.enqueue('https://instagram.com/p/test3');
      
      const items = queueManager.getAll();
      
      expect(items).toHaveLength(3);
    });
    
    it('should return empty array when queue is empty', () => {
      const items = queueManager.getAll();
      expect(items).toEqual([]);
    });
  });
  
  describe('get', () => {
    it('should return item by ID', () => {
      const item = queueManager.enqueue('https://instagram.com/p/test');
      
      const retrieved = queueManager.get(item.id);
      
      expect(retrieved?.id).toBe(item.id);
      expect(retrieved?.url).toBe(item.url);
    });
    
    it('should return undefined for non-existent ID', () => {
      const item = queueManager.get('non-existent-id');
      expect(item).toBeUndefined();
    });
  });
  
  describe('subscribe', () => {
    it('should notify subscribers of updates', () => {
      const callback = vi.fn();
      queueManager.subscribe(callback);
      
      queueManager.enqueue('https://instagram.com/p/test');
      
      expect(callback).toHaveBeenCalled();
    });
    
    it('should return unsubscribe function', () => {
      const callback = vi.fn();
      const unsubscribe = queueManager.subscribe(callback);
      
      queueManager.enqueue('https://instagram.com/p/test1');
      expect(callback).toHaveBeenCalledTimes(1);
      
      unsubscribe();
      callback.mockClear();
      
      queueManager.enqueue('https://instagram.com/p/test2');
      expect(callback).not.toHaveBeenCalled();
    });
    
    it('should handle subscriber errors gracefully', () => {
      const goodCallback = vi.fn();
      const badCallback = vi.fn(() => {
        throw new Error('Subscriber error');
      });
      
      queueManager.subscribe(goodCallback);
      queueManager.subscribe(badCallback);
      
      // Should not throw despite bad callback
      expect(() => {
        queueManager.enqueue('https://instagram.com/p/test');
      }).not.toThrow();
      
      // Good callback should still be called
      expect(goodCallback).toHaveBeenCalled();
    });
    
    it('should support multiple subscribers', () => {
      const callback1 = vi.fn();
      const callback2 = vi.fn();
      const callback3 = vi.fn();
      
      queueManager.subscribe(callback1);
      queueManager.subscribe(callback2);
      queueManager.subscribe(callback3);
      
      queueManager.enqueue('https://instagram.com/p/test');
      
      expect(callback1).toHaveBeenCalled();
      expect(callback2).toHaveBeenCalled();
      expect(callback3).toHaveBeenCalled();
    });
  });
});
