/**
 * Comprehensive End-to-End Integration Tests
 * Tests complete workflows with all features integrated
 */

import { describe, it, expect, beforeAll } from '@jest/globals';
import {
  initTestServer,
  authenticatedRequest,
  unauthenticatedRequest,
  waitForServer,
} from './helpers/test-server';

describe('Comprehensive E2E Integration Tests', () => {
  beforeAll(async () => {
    initTestServer();
    const isReady = await waitForServer(30000);
    if (!isReady) {
      throw new Error('Test server is not ready. Make sure the server is running.');
    }
  });

  describe('Complete Run Lifecycle', () => {
    it('should handle full run lifecycle: create → start → monitor → complete', async () => {
      // Step 1: Create run
      const createResponse = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          name: 'Full Lifecycle Test',
          input: { 
            task: 'process_data',
            data: { items: [1, 2, 3] }
          },
        }),
      });

      expect([201, 429]).toContain(createResponse.status);
      if (createResponse.status === 429) {
        return; // Skip if rate limited
      }
      const createData = await createResponse.json() as { run: { id: string; status: string } };
      const runId = createData.run.id;
      expect(['created', 'pending']).toContain(createData.run.status); // Accept both statuses

      // Step 2: Start run with priority
      const startResponse = await authenticatedRequest(`/api/runs/${runId}/start`, {
        method: 'POST',
        body: JSON.stringify({
          priority: 'high',
        }),
      });

      expect(startResponse.status).toBe(200);
      const startData = await startResponse.json() as { queued?: boolean };
      expect(startData.queued).toBe(true);

      // Step 3: Monitor run status transitions
      let previousStatus = createData.run.status;
      let statusChanged = false;
      let attempts = 0;
      const maxAttempts = 20;

      while (attempts < maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        
        const statusResponse = await authenticatedRequest(`/api/runs/${runId}`);
        const statusData = await statusResponse.json() as { status: string };
        const currentStatus = statusData.status;

        if (currentStatus !== previousStatus) {
          statusChanged = true;
          previousStatus = currentStatus;
        }

        // Run should progress through states
        expect(['created', 'queued', 'running', 'completed', 'failed']).toContain(currentStatus);

        if (currentStatus === 'completed' || currentStatus === 'failed') {
          break;
        }

        attempts++;
      }

      // Verify status actually changed
      expect(statusChanged).toBe(true);

      // Step 4: Verify final state
      const finalResponse = await authenticatedRequest(`/api/runs/${runId}`);
      const finalData = await finalResponse.json() as { status: string; output?: any; completedAt?: string };
      
      expect(['completed', 'failed', 'running', 'queued']).toContain(finalData.status);
      
      if (finalData.status === 'completed') {
        expect(finalData).toHaveProperty('output');
        expect(finalData).toHaveProperty('completedAt');
      }
    });

    it('should handle multiple concurrent runs', async () => {
      const runIds: string[] = [];
      const promises: Promise<Response>[] = [];

      // Create 5 runs concurrently
      for (let i = 0; i < 5; i++) {
        promises.push(
          authenticatedRequest('/api/runs', {
            method: 'POST',
            body: JSON.stringify({
              name: `Concurrent Run ${i}`,
              input: { index: i, concurrent: true },
            }),
          })
        );
      }

      const responses = await Promise.all(promises);
      
      for (const response of responses) {
        expect([201, 429]).toContain(response.status);
        if (response.status === 201) {
          const data = await response.json() as { run: { id: string } };
          runIds.push(data.run.id);
        }
      }

      // At least some runs should have been created
      expect(runIds.length).toBeGreaterThan(0);

      // Start all runs
      const startPromises = runIds.map(runId =>
        authenticatedRequest(`/api/runs/${runId}/start`, {
          method: 'POST',
          body: JSON.stringify({ priority: 'normal' }),
        })
      );

      const startResponses = await Promise.all(startPromises);
      for (const response of startResponses) {
        expect([200, 201]).toContain(response.status);
      }

      // Verify all runs are in valid states
      for (const runId of runIds) {
        const statusResponse = await authenticatedRequest(`/api/runs/${runId}`);
        const statusData = await statusResponse.json() as { status: string };
        expect(['created', 'queued', 'running', 'completed', 'failed']).toContain(statusData.status);
      }
    });
  });

  describe('Priority Queue Handling', () => {
    it('should process high priority runs before normal priority', async () => {
      // Create normal priority run first
      const normalResponse = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          name: 'Normal Priority Run',
          input: { priority: 'normal' },
        }),
      });
      expect([201, 429]).toContain(normalResponse.status);
      if (normalResponse.status === 429) {
        return; // Skip if rate limited
      }
      const normalData = await normalResponse.json() as { run: { id: string } };
      const normalRunId = normalData.run.id;

      // Create high priority run second
      const highResponse = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          name: 'High Priority Run',
          input: { priority: 'high' },
        }),
      });
      expect([201, 429]).toContain(highResponse.status);
      if (highResponse.status === 429) {
        return; // Skip if rate limited
      }
      const highData = await highResponse.json() as { run: { id: string } };
      const highRunId = highData.run.id;

      // Start normal priority first
      await authenticatedRequest(`/api/runs/${normalRunId}/start`, {
        method: 'POST',
        body: JSON.stringify({ priority: 'normal' }),
      });

      // Start high priority second
      await authenticatedRequest(`/api/runs/${highRunId}/start`, {
        method: 'POST',
        body: JSON.stringify({ priority: 'high' }),
      });

      // Wait a bit for queue processing
      await new Promise(resolve => setTimeout(resolve, 2000));

      // Check statuses - high priority should be processed first (if queue is working)
      const highStatusResponse = await authenticatedRequest(`/api/runs/${highRunId}`);
      const highStatusData = await highStatusResponse.json() as { run?: { status: string }; status?: string };
      const highRun = highStatusData.run || highStatusData;
      
      const normalStatusResponse = await authenticatedRequest(`/api/runs/${normalRunId}`);
      const normalStatusData = await normalStatusResponse.json() as { run?: { status: string }; status?: string };
      const normalRun = normalStatusData.run || normalStatusData;

      // Both should be in valid states
      expect(['created', 'queued', 'running', 'completed', 'failed']).toContain(highRun.status);
      expect(['created', 'queued', 'running', 'completed', 'failed']).toContain(normalRun.status);
    });
  });

  describe('Error Handling and Edge Cases', () => {
    it('should handle invalid run ID format with proper error response', async () => {
      // Invalid UUID format returns 400 (validation error) or 429 if rate limited
      const response = await authenticatedRequest('/api/runs/non-existent-id-12345', {}, false);
      expect([400, 429]).toContain(response.status);
      
      const data = await response.json() as { error: string; message: string; correlationId?: string };
      expect(data).toHaveProperty('error');
      expect(data).toHaveProperty('message');
    });

    it('should handle valid UUID format but non-existent run with 404', async () => {
      // Valid UUID format but non-existent run returns 404 or 429 if rate limited
      const response = await authenticatedRequest('/api/runs/00000000-0000-0000-0000-000000000000', {}, false);
      expect([404, 429]).toContain(response.status);
      
      const data = await response.json() as { error: string; message?: string };
      expect(data).toHaveProperty('error');
    });

    it('should validate request body and return detailed errors', async () => {
      const response = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          // Missing required 'name' field
          input: { test: 'invalid' },
        }),
      });

      expect([400, 429]).toContain(response.status);
      const data = await response.json() as { error: string; details?: any; correlationId?: string };
      if (response.status === 400) {
        expect(data).toHaveProperty('error');
        expect(data).toHaveProperty('details');
        expect(data).toHaveProperty('correlationId');
      }
    });

    it('should handle cancellation of queued runs', async () => {
      // Create and start a run
      const createResponse = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          name: 'Cancellation Test Run',
          input: { test: 'cancel' },
        }),
      });

      expect([201, 429]).toContain(createResponse.status);
      if (createResponse.status === 429) {
        return; // Skip if rate limited
      }
      const createData = await createResponse.json() as { run: { id: string } };
      const runId = createData.run.id;

      // Start the run
      await authenticatedRequest(`/api/runs/${runId}/start`, {
        method: 'POST',
        body: JSON.stringify({ priority: 'normal' }),
      });

      // Wait a moment
      await new Promise(resolve => setTimeout(resolve, 500));

      // Cancel the run
      const cancelResponse = await authenticatedRequest(`/api/runs/${runId}/cancel`, {
        method: 'POST',
        body: JSON.stringify({
          reason: 'test_cancellation',
        }),
      });

      // Should accept cancellation
      expect([200, 202, 400]).toContain(cancelResponse.status);

      // Verify run status after cancellation
      const statusResponse = await authenticatedRequest(`/api/runs/${runId}`);
      const statusData = await statusResponse.json() as { status: string };
      expect(['cancelled', 'killed', 'queued', 'running', 'completed', 'failed']).toContain(statusData.status);
    });
  });

  describe('API Consistency', () => {
    it('should return consistent response formats', async () => {
      // Create run
      const createResponse = await authenticatedRequest('/api/runs', {
        method: 'POST',
        body: JSON.stringify({
          name: 'Consistency Test',
          input: { test: 'consistency' },
        }),
      });

      expect([201, 429]).toContain(createResponse.status);
      if (createResponse.status === 429) {
        return; // Skip if rate limited
      }

      const createData = await createResponse.json() as any;
      
      // Verify response structure (create returns { success, run })
      expect(createData).toHaveProperty('success');
      expect(createData).toHaveProperty('run');
      expect(createData.run).toHaveProperty('id');
      expect(createData.run).toHaveProperty('name');
      expect(createData.run).toHaveProperty('status');
      expect(createData.run).toHaveProperty('createdAt');
      expect(typeof createData.run.id).toBe('string');
      expect(typeof createData.run.name).toBe('string');
      expect(typeof createData.run.status).toBe('string');

      // Get run (returns run directly, not wrapped)
      const getResponse = await authenticatedRequest(`/api/runs/${createData.run.id}`);
      const getData = await getResponse.json() as any;
      
      // Verify consistent structure (GET returns run directly)
      expect(getData).toHaveProperty('id');
      expect(getData).toHaveProperty('name');
      expect(getData).toHaveProperty('status');
      expect(getData.id).toBe(createData.run.id);
      expect(getData.name).toBe(createData.run.name);
    });

    it('should include correlation IDs in error responses', async () => {
      const response = await authenticatedRequest('/api/runs/invalid-format', {}, false);
      const data = await response.json() as { correlationId?: string; error?: string };
      
      // Error responses should include correlation ID (if not rate limited)
      if (response.status !== 429) {
        expect(data).toHaveProperty('correlationId');
        expect(typeof data.correlationId).toBe('string');
        expect(data.correlationId?.length).toBeGreaterThan(0);
      }
    });
  });

  describe('Health and Readiness', () => {
    it('should respond to health checks', async () => {
      const response = await unauthenticatedRequest('/health');
      expect(response.status).toBe(200);
      
      const data = await response.json() as { status?: string };
      expect(data).toHaveProperty('status');
    });

    it('should respond to readiness checks', async () => {
      const response = await unauthenticatedRequest('/ready');
      expect(response.status).toBe(200);
      
      const data = await response.json() as { ready?: boolean; checks?: any };
      expect(data).toHaveProperty('ready');
    });
  });
});

