/**
 * Integration tests for Queue API endpoints
 * 
 * Tests the HTTP API routes for queue operations by directly invoking the handlers.
 */

import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { queueManager } from '$lib/server/queue/QueueManager';
import { POST as queuePOST, GET as queueGET } from '../routes/api/queue/+server.js';
import { GET as itemGET, DELETE as itemDELETE } from '../routes/api/queue/[id]/+server.js';
import { POST as retryPOST } from '../routes/api/queue/[id]/retry/+server.js';

describe('Queue API Endpoints', () => {
  beforeEach(() => {
    // Clear queue between tests
    queueManager.getAll().forEach(item => queueManager.remove(item.id));
  });
  
  afterEach(() => {
    // Clean up after tests
    queueManager.getAll().forEach(item => queueManager.remove(item.id));
  });
  
  describe('POST /api/queue', () => {
    it('should enqueue valid Instagram URL', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          url: 'https://instagram.com/p/ABC123'
        })
      });
      
      const response = await queuePOST({ request } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.id).toBeTruthy();
      expect(data.url).toBe('https://instagram.com/p/ABC123');
      expect(data.status).toBe('pending');
      expect(data.enqueuedAt).toBeTruthy();
      
      // Verify item exists in queue
      const item = queueManager.get(data.id);
      expect(item).toBeTruthy();
      expect(item?.url).toBe('https://instagram.com/p/ABC123');
    });
    
    it('should accept Instagram URLs with www', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          url: 'https://www.instagram.com/p/XYZ789'
        })
      });
      
      const response = await queuePOST({ request } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.url).toBe('https://www.instagram.com/p/XYZ789');
      
      // Verify item exists in queue
      const item = queueManager.get(data.id);
      expect(item).toBeTruthy();
      expect(item?.url).toBe('https://www.instagram.com/p/XYZ789');
    });
    
    it('should accept Instagram reel URLs', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          url: 'https://instagram.com/reel/ABC123'
        })
      });
      
      const response = await queuePOST({ request } as any);
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.url).toBe('https://instagram.com/reel/ABC123');
    });

    it('should accept Instagram URLs with query parameters', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          url: 'https://www.instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link'
        })
      });
      
      const response = await queuePOST({ request } as any);
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.url).toBe('https://www.instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link');
    });

    it('should accept Instagram IGTV URLs', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          url: 'https://instagram.com/tv/XYZ789'
        })
      });
      
      const response = await queuePOST({ request } as any);
      expect(response.status).toBe(200);
    });

    it('should reject HTTP (non-HTTPS) URLs', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          url: 'http://instagram.com/p/ABC123'
        })
      });
      
      try {
        const response = await queuePOST({ request } as any);
        expect(response.status).toBe(400);
        const data = await response.json();
        expect(data.message).toContain('HTTPS');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toContain('HTTPS');
      }
    });
    
    it('should reject invalid Instagram URL formats', async () => {
      const invalidUrls = [
        'https://facebook.com/post/123',
        'not-a-url',
        'https://other-site.com'
      ];
      
      for (const url of invalidUrls) {
        const request = new Request('http://localhost/api/queue', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ url })
        });
        
        try {
          const response = await queuePOST({ request } as any);
          // If we get here, check the response status
          expect(response.status).toBe(400);
          const data = await response.json();
          // Updated to check for new error messages
          expect(data.message).toBeTruthy();
        } catch (err: any) {
          // SvelteKit's error() throws - check the error
          expect(err.status).toBe(400);
          expect(err.body.message).toBeTruthy();
        }
      }
      
      // Verify no items were added to queue
      expect(queueManager.getAll()).toHaveLength(0);
    });
    
    it('should reject non-Instagram domains', async () => {
      const invalidUrls = [
        'https://facebook.com/post/123',
        'https://twitter.com/status/456',
        'https://example.com',
        'https://instagram.com.evil.com/p/123'
      ];
      
      for (const url of invalidUrls) {
        const request = new Request('http://localhost/api/queue', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ url })
        });
        
        try {
          const response = await queuePOST({ request } as any);
          expect(response.status).toBe(400);
          const data = await response.json();
          expect(data.message).toContain('instagram.com');
        } catch (err: any) {
          expect(err.status).toBe(400);
          expect(err.body.message).toContain('instagram.com');
        }
      }
    });
    
    it('should reject missing URL', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({})
      });
      
      try {
        const response = await queuePOST({ request } as any);
        expect(response.status).toBe(400);
        const data = await response.json();
        expect(data.message).toBe('URL is required and must be a string');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('URL is required and must be a string');
      }
    });
    
    it('should reject non-JSON body', async () => {
      const request = new Request('http://localhost/api/queue', {
        method: 'POST',
        headers: {
          'Content-Type': 'text/plain',
        },
        body: 'not json'
      });
      
      try {
        const response = await queuePOST({ request } as any);
        expect(response.status).toBe(400);
        const data = await response.json();
        expect(data.message).toBe('Invalid JSON in request body');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Invalid JSON in request body');
      }
    });
  });

  describe('GET /api/queue', () => {
    it('should return empty list when no items', async () => {
      const url = new URL('http://localhost/api/queue');
      const request = new Request(url);
      
      const response = await queueGET({ request, url } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.items).toEqual([]);
      expect(data.total).toBe(0);
      expect(data.pagination.offset).toBe(0);
      expect(data.pagination.limit).toBe(50);
    });
    
    it('should return queued items', async () => {
      // Add test items
      const item1 = queueManager.enqueue('https://instagram.com/p/TEST1');
      const item2 = queueManager.enqueue('https://instagram.com/p/TEST2');
      
      const url = new URL('http://localhost/api/queue');
      const request = new Request(url);
      
      const response = await queueGET({ request, url } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.total).toBe(2);
      expect(data.items).toHaveLength(2);
      
      // Sort by URL for order-independent assertions (API sorts by time, newest first)
      const sortedItems = data.items.sort((a: any, b: any) => a.url.localeCompare(b.url));
      expect(sortedItems[0].url).toBe('https://instagram.com/p/TEST1');
      expect(sortedItems[1].url).toBe('https://instagram.com/p/TEST2');
    });
    
    it('should filter by status', async () => {
      // Add test items with different statuses
      const item1 = queueManager.enqueue('https://instagram.com/p/PENDING');
      const item2 = queueManager.enqueue('https://instagram.com/p/ERROR');
      
      // Set one to error status
      queueManager.updateStatus(item2.id, 'error', { message: 'Test error' });
      
      const url = new URL('http://localhost/api/queue?status=error');
      const request = new Request(url);
      
      const response = await queueGET({ request, url } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.total).toBe(1);
      expect(data.items).toHaveLength(1);
      expect(data.items[0].status).toBe('error');
      expect(data.items[0].url).toBe('https://instagram.com/p/ERROR');
    });
    
    it('should handle pagination', async () => {
      // Add multiple test items
      for (let i = 1; i <= 5; i++) {
        queueManager.enqueue(`https://instagram.com/p/TEST${i}`);
      }
      
      const url = new URL('http://localhost/api/queue?limit=2&offset=1');
      const request = new Request(url);
      
      const response = await queueGET({ request, url } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.total).toBe(5);
      expect(data.items).toHaveLength(2);
      expect(data.pagination.offset).toBe(1);
      expect(data.pagination.limit).toBe(2);
      // Items are sorted by enqueued time (newest first), so with offset=1, limit=2 we get items 2-3 from the sorted list
    });
    
    it('should validate query parameters', async () => {
      // Invalid status
      try {
        let url = new URL('http://localhost/api/queue?status=invalid');
        let request = new Request(url);
        let response = await queueGET({ request, url } as any);
        expect(response.status).toBe(400);
        let data = await response.json();
        expect(data.message).toBe('Invalid status filter. Must be one of: pending, in_progress, success, unhealthy, error');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Invalid status filter. Must be one of: pending, in_progress, success, unhealthy, error');
      }
      
      // Invalid limit (negative)
      try {
        let url = new URL('http://localhost/api/queue?limit=-1');
        let request = new Request(url);
        let response = await queueGET({ request, url } as any);
        expect(response.status).toBe(400);
        let data = await response.json();
        expect(data.message).toBe('Limit must be a positive integer');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Limit must be a positive integer');
      }
      
      // Invalid offset (negative)
      try {
        let url = new URL('http://localhost/api/queue?offset=-1');
        let request = new Request(url);
        let response = await queueGET({ request, url } as any);
        expect(response.status).toBe(400);
        let data = await response.json();
        expect(data.message).toBe('Offset must be a non-negative integer');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Offset must be a non-negative integer');
      }
      
      // Limit too large
      try {
        let url = new URL('http://localhost/api/queue?limit=999');
        let request = new Request(url);
        let response = await queueGET({ request, url } as any);
        expect(response.status).toBe(400);
        let data = await response.json();
        expect(data.message).toBe('Limit cannot exceed 200');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Limit cannot exceed 200');
      }
    });
  });

  describe('GET /api/queue/[id]', () => {
    it('should return queue item by ID', async () => {
      // Add test item
      const item = queueManager.enqueue('https://instagram.com/p/DETAIL');
      
      const response = await itemGET({ 
        params: { id: item.id } 
      } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.id).toBe(item.id);
      expect(data.url).toBe('https://instagram.com/p/DETAIL');
      expect(data.status).toBe('pending');
    });
    
    it('should return 404 for non-existent ID', async () => {
      const fakeId = '550e8400-e29b-41d4-a716-446655440000'; // Valid v4 UUID format but non-existent
      try {
        const response = await itemGET({ 
          params: { id: fakeId } 
        } as any);
        expect(response.status).toBe(404);
        const data = await response.json();
        expect(data.message).toBe('Queue item not found');
      } catch (err: any) {
        expect(err.status).toBe(404);
        expect(err.body.message).toBe('Queue item not found');
      }
    });
    
    it('should validate ID format', async () => {
      try {
        const response = await itemGET({ 
          params: { id: 'invalid-id' } 
        } as any);
        expect(response.status).toBe(400);
        const data = await response.json();
        expect(data.message).toBe('Invalid queue item ID format');
      } catch (err: any) {
        expect(err.status).toBe(400);
        expect(err.body.message).toBe('Invalid queue item ID format');
      }
    });
  });

  describe('POST /api/queue/[id]/retry', () => {
    it('should retry error item', async () => {
      // Add test item and set to error
      const item = queueManager.enqueue('https://instagram.com/p/RETRY');
      queueManager.updateStatus(item.id, 'error', { message: 'Test error' });
      
      const request = new Request(`http://localhost/api/queue/${item.id}/retry`, {
        method: 'POST'
      });
      
      const response = await retryPOST({
        request,
        params: { id: item.id }
      } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.message).toBe('Queue item has been reset and will be reprocessed');
      expect(data.success).toBe(true);
      
      // Verify item status was reset
      const updatedItem = queueManager.get(item.id);
      expect(updatedItem?.status).toBe('pending');
      expect(updatedItem?.error).toBeUndefined(); // error field is cleared (undefined, not null)
    });
    
    it('should retry unhealthy item', async () => {
      // Add test item and set to unhealthy
      const item = queueManager.enqueue('https://instagram.com/p/UNHEALTHY');
      queueManager.updateStatus(item.id, 'unhealthy', {
        phase: 'extraction',
        attempts: 3,
        lastAttempt: new Date(),
        message: 'Max retries exceeded'
      });
      
      const request = new Request(`http://localhost/api/queue/${item.id}/retry`, {
        method: 'POST'
      });
      
      const response = await retryPOST({
        request,
        params: { id: item.id }
      } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.message).toBe('Queue item has been reset and will be reprocessed');
      expect(data.success).toBe(true);
      
      // Verify item status was reset
      const updatedItem = queueManager.get(item.id);
      expect(updatedItem?.status).toBe('pending');
    });
    
    it('should reject retry for non-retryable statuses', async () => {
      // Add test item (default status is 'pending')
      const item = queueManager.enqueue('https://instagram.com/p/PENDING');
      
      const request = new Request(`http://localhost/api/queue/${item.id}/retry`, {
        method: 'POST'
      });
      
      // Item is pending (cannot retry)
      try {
        const response = await retryPOST({
          request,
          params: { id: item.id }
        } as any);
        expect(response.status).toBe(409);
        const data = await response.json();
        expect(data.message).toBe("Cannot retry item with status 'pending'. Only 'error' and 'unhealthy' items can be retried.");
      } catch (err: any) {
        expect(err.status).toBe(409);
        expect(err.body.message).toBe("Cannot retry item with status 'pending'. Only 'error' and 'unhealthy' items can be retried.");
      }
    });
    
    it('should return 404 for non-existent item', async () => {
      const fakeId = '550e8400-e29b-41d4-a716-446655440000'; // Valid v4 UUID format but non-existent
      const request = new Request(`http://localhost/api/queue/${fakeId}/retry`, {
        method: 'POST'
      });
      
      try {
        const response = await retryPOST({
          request,
          params: { id: fakeId }
        } as any);
        expect(response.status).toBe(404);
        const data = await response.json();
        expect(data.message).toBe('Queue item not found');
      } catch (err: any) {
        expect(err.status).toBe(404);
        expect(err.body.message).toBe('Queue item not found');
      }
    });
  });
  
  describe('DELETE /api/queue/[id]', () => {
    it('should delete queue item successfully', async () => {
      // Create an item
      const item = queueManager.enqueue('https://instagram.com/p/DELETE123');
      
      // Mark it as success (completed)
      queueManager.updateStatus(item.id, 'success');
      
      const request = new Request(`http://localhost/api/queue/${item.id}`, {
        method: 'DELETE'
      });
      
      const response = await itemDELETE({
        request,
        params: { id: item.id }
      } as any);
      
      expect(response.status).toBe(200);
      const data = await response.json();
      expect(data.success).toBe(true);
      expect(data.message).toBe('Queue item removed successfully');
      
      // Verify item no longer exists
      expect(queueManager.get(item.id)).toBeUndefined();
    });
    
    it('should return 404 for non-existent item', async () => {
      const fakeId = '550e8400-e29b-41d4-a716-446655440000';
      const request = new Request(`http://localhost/api/queue/${fakeId}`, {
        method: 'DELETE'
      });
      
      try {
        const response = await itemDELETE({
          request,
          params: { id: fakeId }
        } as any);
        expect(response.status).toBe(404);
        const data = await response.json();
        expect(data.message).toBe('Queue item not found');
      } catch (err: any) {
        expect(err.status).toBe(404);
        expect(err.body.message).toBe('Queue item not found');
      }
    });
    
    it('should return 409 for in-progress items', async () => {
      // Create an item and mark it as in progress
      const item = queueManager.enqueue('https://instagram.com/p/PROCESSING');
      queueManager.updateStatus(item.id, 'in_progress', { phase: 'extraction' });
      
      const request = new Request(`http://localhost/api/queue/${item.id}`, {
        method: 'DELETE'
      });
      
      try {
        const response = await itemDELETE({
          request,
          params: { id: item.id }
        } as any);
        expect(response.status).toBe(409);
        const data = await response.json();
        expect(data.message).toBe('Cannot delete item that is currently being processed');
      } catch (err: any) {
        expect(err.status).toBe(409);
        expect(err.body.message).toBe('Cannot delete item that is currently being processed');
      }
      
      // Verify item still exists
      expect(queueManager.get(item.id)).toBeTruthy();
    });
    
    it('should validate ID format', async () => {
      const invalidIds = ['not-a-uuid', '12345', 'abc-def-ghi'];
      
      for (const invalidId of invalidIds) {
        const request = new Request(`http://localhost/api/queue/${invalidId}`, {
          method: 'DELETE'
        });
        
        try {
          const response = await itemDELETE({
            request,
            params: { id: invalidId }
          } as any);
          expect(response.status).toBe(400);
          const data = await response.json();
          expect(data.message).toBe('Invalid queue item ID format');
        } catch (err: any) {
          expect(err.status).toBe(400);
          expect(err.body.message).toBe('Invalid queue item ID format');
        }
      }
    });
  });
});