import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { DashboardServer } from '../server.js';
import { Server } from 'http';
import { io as ioClient, Socket } from 'socket.io-client';
import request from 'supertest';

// Mock the modules we depend on
vi.mock('../../utils/performance-monitor.js', () => ({
  PerformanceMonitor: {
    getInstance: vi.fn(() => ({
      getSystemMetrics: vi.fn().mockResolvedValue({ cpu: 50, memory: 60 }),
      getToolMetrics: vi.fn().mockResolvedValue([]),
      getQualityMetrics: vi.fn().mockResolvedValue({ testCoverage: 80 }),
      getPerformanceAlerts: vi.fn().mockResolvedValue([])
    }))
  }
}));

vi.mock('../../utils/security-manager.js', () => ({
  SecurityManager: {
    getInstance: vi.fn(() => ({
      getSecurityStatus: vi.fn().mockResolvedValue({ level: 'medium' }),
      getSecurityEvents: vi.fn().mockResolvedValue([]),
      generateSecurityMetrics: vi.fn().mockResolvedValue({ totalEvents: 0 })
    }))
  }
}));

vi.mock('../../utils/error-handler.js', () => ({
  ErrorHandler: vi.fn().mockImplementation(() => ({
    getErrorTimeline: vi.fn().mockResolvedValue([]),
    analyzeErrorPatterns: vi.fn().mockResolvedValue({ patterns: [] })
  }))
}));

vi.mock('../../modules/agile-management/agile-manager.js', () => ({
  AgileManager: vi.fn().mockImplementation(() => ({
    getAllSprints: vi.fn().mockResolvedValue([]),
    getAllStories: vi.fn().mockResolvedValue([]),
    getAllEpics: vi.fn().mockResolvedValue([])
  }))
}));

// Mock the SQLite manager to prevent database initialization issues
vi.mock('../../storage/sqlite-manager.js', () => ({
  getSQLiteManager: vi.fn(() => ({
    query: vi.fn().mockResolvedValue({ success: true, data: [] }),
    get: vi.fn().mockResolvedValue({ success: true, data: null }),
    run: vi.fn().mockResolvedValue({ success: true, data: { changes: 1 } }),
    initialize: vi.fn().mockResolvedValue(true),
    close: vi.fn().mockResolvedValue(true)
  })),
  ensureDatabaseReady: vi.fn().mockResolvedValue({
    query: vi.fn().mockResolvedValue({ success: true, data: [] }),
    get: vi.fn().mockResolvedValue({ success: true, data: null }),
    run: vi.fn().mockResolvedValue({ success: true, data: { changes: 1 } }),
    initialize: vi.fn().mockResolvedValue(true),
    close: vi.fn().mockResolvedValue(true),
    getConnectionInfo: vi.fn().mockReturnValue({
      isInitialized: true,
      dbPath: ':memory:'
    })
  })
}));

describe('DashboardServer', () => {
  let dashboardServer: DashboardServer;
  let httpServer: Server;
  let clientSocket: Socket;
  let testPort: number;
  let mockPerformanceMonitor: any;
  let mockSecurityManager: any;
  let mockErrorHandler: any;
  let mockAgileManager: any;
  
  // Generate a unique port for each test run
  const getRandomPort = () => Math.floor(Math.random() * 10000) + 40000;

  beforeEach(async () => {
    // Generate a unique port for this test
    testPort = getRandomPort();
    
    // Create mocks for dependencies
    mockPerformanceMonitor = {
      getSystemMetrics: vi.fn().mockResolvedValue({ cpu: 50, memory: 60 }),
      getToolMetrics: vi.fn().mockResolvedValue([]),
      getQualityMetrics: vi.fn().mockResolvedValue({ testCoverage: 80 }),
      getPerformanceAlerts: vi.fn().mockResolvedValue([])
    };
    
    mockSecurityManager = {
      getSecurityStatus: vi.fn().mockResolvedValue({ level: 'medium' }),
      getSecurityEvents: vi.fn().mockResolvedValue([]),
      generateSecurityMetrics: vi.fn().mockResolvedValue({ totalEvents: 0 })
    };
    
    mockErrorHandler = {
      getErrorTimeline: vi.fn().mockResolvedValue([]),
      analyzeErrorPatterns: vi.fn().mockResolvedValue({ patterns: [] })
    };
    
    mockAgileManager = {
      getAllSprints: vi.fn().mockResolvedValue([]),
      getAllStories: vi.fn().mockResolvedValue([]),
      getAllEpics: vi.fn().mockResolvedValue([])
    };
    
    const config = {
      enabled: true,
      port: testPort,
      host: 'localhost',
      autoOpen: false,
      features: {
        performance: true,
        security: true,
        agile: true,
        errors: true
      },
      realTimeUpdates: true,
      exportEnabled: true
    };
    
    dashboardServer = new DashboardServer(
      config,
      mockPerformanceMonitor,
      mockSecurityManager,
      mockErrorHandler,
      mockAgileManager
    );
    await dashboardServer.start();
    httpServer = dashboardServer['server']; // Access private server property for testing
    
    // Give the server time to start
    await new Promise(resolve => setTimeout(resolve, 100));
  });

  afterEach(async () => {
    if (clientSocket && clientSocket.connected) {
      clientSocket.disconnect();
      clientSocket = null;
    }
    
    if (dashboardServer) {
      await dashboardServer.stop();
      // Add a small delay to ensure port is released
      await new Promise(resolve => setTimeout(resolve, 100));
    }
  });

  describe('Server Lifecycle', () => {
    it('should start the server on specified port', async () => {
      const response = await request(`http://localhost:${testPort}`).get('/');
      expect(response.status).toBe(200);
      expect(response.type).toBe('text/html');
    });

    it('should serve static files', async () => {
      const response = await request(`http://localhost:${testPort}`).get('/css/dashboard.css');
      expect(response.status).toBe(200);
      expect(response.type).toBe('text/css');
    });

    it('should handle health check endpoint', async () => {
      const response = await request(`http://localhost:${testPort}`).get('/api/health');
      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        status: 'healthy',
        timestamp: expect.any(String),
        features: expect.any(Object),
        version: expect.any(String),
        database: expect.any(Object)
      });
    });

    it('should stop gracefully', async () => {
      // Create a separate instance for this test
      const stopTestPort = getRandomPort();
      const stopTestServer = new DashboardServer(
        {
          enabled: true,
          port: stopTestPort,
          host: 'localhost',
          autoOpen: false,
          features: {
            performance: true,
            security: true,
            agile: true,
            errors: true
          },
          realTimeUpdates: false,
          exportEnabled: true
        },
        mockPerformanceMonitor,
        mockSecurityManager,
        mockErrorHandler,
        mockAgileManager
      );
      
      await stopTestServer.start();
      await stopTestServer.stop();
      
      // Try to connect - should fail
      try {
        await request(`http://localhost:${stopTestPort}`).get('/');
        expect.fail('Server should be stopped');
      } catch (error) {
        // Expected - server is stopped
        expect(error).toBeDefined();
      }
    });
  });

  describe('WebSocket Communication', () => {
    beforeEach((done) => {
      clientSocket = ioClient(`http://localhost:${testPort}`, {
        transports: ['websocket'],
        autoConnect: true
      });
      
      clientSocket.on('connect', done);
    });

    it('should accept websocket connections', () => {
      // The beforeEach already establishes the connection via done callback
      // So if we reach this test, the connection was successful
      expect(clientSocket).toBeDefined();
      expect(clientSocket.io).toBeDefined();
    });

    it('should send initial data on connection', (done) => {
      clientSocket.on('initial-data', (data: any) => {
        expect(data).toHaveProperty('metrics');
        expect(data).toHaveProperty('security');
        expect(data).toHaveProperty('errors');
        expect(data).toHaveProperty('agile');
        done();
      });
    });

    it('should broadcast dashboard updates', (done) => {
      clientSocket.on('dashboard-update', (data: any) => {
        expect(data).toHaveProperty('metrics');
        expect(data).toHaveProperty('timestamp');
        done();
      });
      
      // Trigger an update by waiting for the interval
      setTimeout(() => {
        // Update should have been broadcast by now
      }, 2000);
    });

    it('should handle client disconnection', (done) => {
      clientSocket.on('disconnect', () => {
        expect(clientSocket.connected).toBe(false);
        done();
      });
      
      clientSocket.disconnect();
    });

    it('should handle request-data event', (done) => {
      clientSocket.emit('request-data', { type: 'metrics' });
      
      clientSocket.on('data-response', (data: any) => {
        expect(data.type).toBe('metrics');
        expect(data.data).toBeDefined();
        done();
      });
    });
  });

  describe('API Integration', () => {
    it('should serve metrics API', async () => {
      const response = await request(`http://localhost:${testPort}`)
        .get('/api/metrics/overview');
      
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('cpu');
    });

    it('should serve security API', async () => {
      const response = await request(`http://localhost:${testPort}`)
        .get('/api/security/status');
      
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('level');
    });

    it('should serve agile API', async () => {
      const response = await request(`http://localhost:${testPort}`)
        .get('/api/agile/sprints');
      
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      // The API returns data object with sprints array
      expect(response.body.data).toBeDefined();
      expect(response.body.data.sprints).toBeInstanceOf(Array);
    });

    it('should serve errors API', async () => {
      // Add a small delay to ensure server is ready
      await new Promise(resolve => setTimeout(resolve, 100));
      
      const response = await request(`http://localhost:${testPort}`)
        .get('/api/errors/timeline')
        .timeout(5000); // Add explicit timeout
      
      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('timeline');
    }, 10000); // Increase test timeout
  });

  describe('Error Handling', () => {
    it('should handle 404 for unknown routes', async () => {
      const response = await request(`http://localhost:${testPort}`)
        .get('/unknown/route');
      
      expect(response.status).toBe(404);
    });

    it('should handle API errors gracefully', async () => {
      // Force an error by mocking a failure
      mockPerformanceMonitor.getSystemMetrics.mockRejectedValueOnce(new Error('Test error'));
      
      const response = await request(`http://localhost:${testPort}`)
        .get('/api/metrics/overview');
      
      expect(response.status).toBe(500);
      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('Failed to fetch system metrics');
    });
  });

  describe('Data Collection', () => {
    it('should collect data periodically', async () => {
      const initialDataSpy = vi.fn();
      
      // Connect a client to capture updates
      const testClient = ioClient(`http://localhost:${testPort}`, {
        transports: ['websocket']
      });
      
      testClient.on('performance_update', initialDataSpy);
      
      // Wait for at least one update cycle (6 seconds to be safe)
      await new Promise(resolve => setTimeout(resolve, 6000));
      
      expect(initialDataSpy).toHaveBeenCalled();
      
      testClient.disconnect();
    }, 15000); // Set timeout to 15 seconds
  });

  describe('Configuration', () => {
    it('should use custom configuration when provided', async () => {
      const customPort = getRandomPort();
      const customConfig = {
        enabled: true,
        port: customPort,
        host: 'localhost',
        autoOpen: false,
        features: {
          performance: true,
          security: true,
          agile: true,
          errors: true
        },
        realTimeUpdates: true,
        exportEnabled: true,
        updateInterval: 5000,
        maxDataPoints: 200
      };
      
      const customDashboard = new DashboardServer(
        customConfig,
        mockPerformanceMonitor,
        mockSecurityManager,
        mockErrorHandler,
        mockAgileManager
      );
      
      await customDashboard.start();
      
      // Verify server started with custom config
      const response = await request(`http://localhost:${customPort}`).get('/api/health');
      expect(response.status).toBe(200);
      
      await customDashboard.stop();
    });
  });
});