/**
 * Integration Test: API Connection Monitor Health Checks
 *
 * Tests the orchestration logic of API connection health monitoring:
 * - ApiConnectionMonitor: Tracks connection status, performs health checks, records metrics
 * - Validates that health monitoring correctly interacts with its Ports (API endpoints, event emitters)
 * - Uses In-Memory adapters for fast, deterministic testing
 *
 * Focus: Business logic orchestration, NOT UI rendering
 */

import { describe, it, expect, beforeAll, beforeEach, afterEach, vi } from 'vitest';
import { InMemoryHealthCheckAdapter } from '../../../adapters/health/persistence/inmemory/InMemoryHealthCheckAdapter';
import { InMemoryHealthEventPublisher } from '../../../adapters/events/InMemoryHealthEventPublisher';
import { ApiConnectionMonitor } from '../../../apps/website/lib/api/base/ApiConnectionMonitor';

// Mock fetch to use our in-memory adapter
const mockFetch = vi.fn();
global.fetch = mockFetch as any;

describe('API Connection Monitor Health Orchestration', () => {
  let healthCheckAdapter: InMemoryHealthCheckAdapter;
  let eventPublisher: InMemoryHealthEventPublisher;
  let apiConnectionMonitor: ApiConnectionMonitor;

  beforeAll(() => {
    // Initialize In-Memory health check adapter and event publisher
    healthCheckAdapter = new InMemoryHealthCheckAdapter();
    eventPublisher = new InMemoryHealthEventPublisher();
  });

  beforeEach(() => {
    // Reset the singleton instance
    (ApiConnectionMonitor as any).instance = undefined;
    
    // Create a new instance for each test
    apiConnectionMonitor = ApiConnectionMonitor.getInstance('/health');
    
    // Clear all In-Memory repositories before each test
    healthCheckAdapter.clear();
    eventPublisher.clear();
    
    // Reset mock fetch
    mockFetch.mockReset();
    
    // Mock fetch to use our in-memory adapter
    mockFetch.mockImplementation(async (url: string) => {
      // Simulate network delay
      await new Promise(resolve => setTimeout(resolve, 50));
      
      // Check if we should fail
      if (healthCheckAdapter.shouldFail) {
        throw new Error(healthCheckAdapter.failError);
      }
      
      // Return successful response
      return {
        ok: true,
        status: 200,
      };
    });
  });

  afterEach(() => {
    // Stop any ongoing monitoring
    apiConnectionMonitor.stopMonitoring();
  });

  describe('PerformHealthCheck - Success Path', () => {
    it('should perform successful health check and record metrics', async () => {
      // Scenario: API is healthy and responsive
      // Given: HealthCheckAdapter returns successful response
      // And: Response time is 50ms
      healthCheckAdapter.setResponseTime(50);
      
      // Mock fetch to return successful response
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check result should show healthy=true
      expect(result.healthy).toBe(true);
      
      // And: Response time should be recorded
      expect(result.responseTime).toBeGreaterThanOrEqual(50);
      expect(result.timestamp).toBeInstanceOf(Date);
      
      // And: Connection status should be 'connected'
      expect(apiConnectionMonitor.getStatus()).toBe('connected');
      
      // And: Metrics should be recorded
      const health = apiConnectionMonitor.getHealth();
      expect(health.totalRequests).toBe(1);
      expect(health.successfulRequests).toBe(1);
      expect(health.failedRequests).toBe(0);
      expect(health.consecutiveFailures).toBe(0);
    });

    it('should perform health check with slow response time', async () => {
      // Scenario: API is healthy but slow
      // Given: HealthCheckAdapter returns successful response
      // And: Response time is 500ms
      healthCheckAdapter.setResponseTime(500);
      
      // Mock fetch to return successful response
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check result should show healthy=true
      expect(result.healthy).toBe(true);
      
      // And: Response time should be recorded as 500ms
      expect(result.responseTime).toBeGreaterThanOrEqual(500);
      expect(result.timestamp).toBeInstanceOf(Date);
      
      // And: Connection status should be 'connected'
      expect(apiConnectionMonitor.getStatus()).toBe('connected');
    });

    it('should handle multiple successful health checks', async () => {
      // Scenario: Multiple consecutive successful health checks
      // Given: HealthCheckAdapter returns successful responses
      healthCheckAdapter.setResponseTime(50);
      
      // Mock fetch to return successful responses
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called 3 times
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();

      // Then: All health checks should show healthy=true
      const health = apiConnectionMonitor.getHealth();
      expect(health.totalRequests).toBe(3);
      expect(health.successfulRequests).toBe(3);
      expect(health.failedRequests).toBe(0);
      expect(health.consecutiveFailures).toBe(0);
      
      // And: Average response time should be calculated
      expect(health.averageResponseTime).toBeGreaterThanOrEqual(50);
    });
  });

  describe('PerformHealthCheck - Failure Path', () => {
    it('should handle failed health check and record failure', async () => {
      // Scenario: API is unreachable
      // Given: HealthCheckAdapter throws network error
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check result should show healthy=false
      expect(result.healthy).toBe(false);
      expect(result.error).toBeDefined();
      
      // And: Connection status should be 'disconnected'
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
      
      // And: Consecutive failures should be 1
      const health = apiConnectionMonitor.getHealth();
      expect(health.consecutiveFailures).toBe(1);
      expect(health.totalRequests).toBe(1);
      expect(health.failedRequests).toBe(1);
      expect(health.successfulRequests).toBe(0);
    });

    it('should handle multiple consecutive failures', async () => {
      // Scenario: API is down for multiple checks
      // Given: HealthCheckAdapter throws errors 3 times
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // When: performHealthCheck() is called 3 times
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();

      // Then: All health checks should show healthy=false
      const health = apiConnectionMonitor.getHealth();
      expect(health.totalRequests).toBe(3);
      expect(health.failedRequests).toBe(3);
      expect(health.successfulRequests).toBe(0);
      expect(health.consecutiveFailures).toBe(3);
      
      // And: Connection status should be 'disconnected'
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
    });

    it('should handle timeout during health check', async () => {
      // Scenario: Health check times out
      // Given: HealthCheckAdapter times out after 30 seconds
      mockFetch.mockImplementation(() => {
        return new Promise((_, reject) => {
          setTimeout(() => reject(new Error('Timeout')), 3000);
        });
      });

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check result should show healthy=false
      expect(result.healthy).toBe(false);
      expect(result.error).toContain('Timeout');
      
      // And: Consecutive failures should increment
      const health = apiConnectionMonitor.getHealth();
      expect(health.consecutiveFailures).toBe(1);
    });
  });

  describe('Connection Status Management', () => {
    it('should transition from disconnected to connected after recovery', async () => {
      // Scenario: API recovers from outage
      // Given: Initial state is disconnected with 3 consecutive failures
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));
      
      // Perform 3 failed checks to get disconnected status
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
      
      // And: HealthCheckAdapter starts returning success
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called
      await apiConnectionMonitor.performHealthCheck();

      // Then: Connection status should transition to 'connected'
      expect(apiConnectionMonitor.getStatus()).toBe('connected');
      
      // And: Consecutive failures should reset to 0
      const health = apiConnectionMonitor.getHealth();
      expect(health.consecutiveFailures).toBe(0);
    });

    it('should degrade status when reliability drops below threshold', async () => {
      // Scenario: API has intermittent failures
      // Given: 5 successful requests followed by 3 failures
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // Perform 5 successful checks
      for (let i = 0; i < 5; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // Now start failing
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // Perform 3 failed checks
      for (let i = 0; i < 3; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // Then: Connection status should be 'degraded'
      expect(apiConnectionMonitor.getStatus()).toBe('degraded');
      
      // And: Reliability should be calculated correctly (5/8 = 62.5%)
      const health = apiConnectionMonitor.getHealth();
      expect(health.totalRequests).toBe(8);
      expect(health.successfulRequests).toBe(5);
      expect(health.failedRequests).toBe(3);
      expect(apiConnectionMonitor.getReliability()).toBeCloseTo(62.5, 1);
    });

    it('should handle checking status when no requests yet', async () => {
      // Scenario: Monitor just started
      // Given: No health checks performed yet
      // When: getStatus() is called
      const status = apiConnectionMonitor.getStatus();

      // Then: Status should be 'checking'
      expect(status).toBe('checking');
      
      // And: isAvailable() should return false
      expect(apiConnectionMonitor.isAvailable()).toBe(false);
    });
  });

  describe('Health Metrics Calculation', () => {
    it('should correctly calculate reliability percentage', async () => {
      // Scenario: Calculate reliability from mixed results
      // Given: 7 successful requests and 3 failed requests
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // Perform 7 successful checks
      for (let i = 0; i < 7; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // Now start failing
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // Perform 3 failed checks
      for (let i = 0; i < 3; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // When: getReliability() is called
      const reliability = apiConnectionMonitor.getReliability();

      // Then: Reliability should be 70%
      expect(reliability).toBeCloseTo(70, 1);
    });

    it('should correctly calculate average response time', async () => {
      // Scenario: Calculate average from varying response times
      // Given: Response times of 50ms, 100ms, 150ms
      const responseTimes = [50, 100, 150];
      
      // Mock fetch with different response times
      mockFetch.mockImplementation(() => {
        const time = responseTimes.shift() || 50;
        return new Promise(resolve => {
          setTimeout(() => {
            resolve({
              ok: true,
              status: 200,
            });
          }, time);
        });
      });

      // Perform 3 health checks
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();

      // When: getHealth() is called
      const health = apiConnectionMonitor.getHealth();

      // Then: Average response time should be 100ms
      expect(health.averageResponseTime).toBeCloseTo(100, 1);
    });

    it('should handle zero requests for reliability calculation', async () => {
      // Scenario: No requests made yet
      // Given: No health checks performed
      // When: getReliability() is called
      const reliability = apiConnectionMonitor.getReliability();

      // Then: Reliability should be 0
      expect(reliability).toBe(0);
    });
  });

  describe('Health Check Endpoint Selection', () => {
    it('should try multiple endpoints when primary fails', async () => {
      // Scenario: Primary endpoint fails, fallback succeeds
      // Given: /health endpoint fails
      // And: /api/health endpoint succeeds
      let callCount = 0;
      mockFetch.mockImplementation(() => {
        callCount++;
        if (callCount === 1) {
          // First call to /health fails
          return Promise.reject(new Error('ECONNREFUSED'));
        } else {
          // Second call to /api/health succeeds
          return Promise.resolve({
            ok: true,
            status: 200,
          });
        }
      });

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check should be successful
      expect(result.healthy).toBe(true);
      
      // And: Connection status should be 'connected'
      expect(apiConnectionMonitor.getStatus()).toBe('connected');
    });

    it('should handle all endpoints being unavailable', async () => {
      // Scenario: All health endpoints are down
      // Given: /health, /api/health, and /status all fail
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Health check should show healthy=false
      expect(result.healthy).toBe(false);
      
      // And: Connection status should be 'disconnected'
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
    });
  });

  describe('Event Emission Patterns', () => {
    it('should emit connected event when transitioning to connected', async () => {
      // Scenario: Successful health check after disconnection
      // Given: Current status is disconnected
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));
      
      // Perform 3 failed checks to get disconnected status
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
      
      // And: HealthCheckAdapter returns success
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called
      await apiConnectionMonitor.performHealthCheck();

      // Then: EventPublisher should emit ConnectedEvent
      // Note: ApiConnectionMonitor emits events directly, not through InMemoryHealthEventPublisher
      // We can verify by checking the status transition
      expect(apiConnectionMonitor.getStatus()).toBe('connected');
    });

    it('should emit disconnected event when threshold exceeded', async () => {
      // Scenario: Consecutive failures reach threshold
      // Given: 2 consecutive failures
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));
      
      await apiConnectionMonitor.performHealthCheck();
      await apiConnectionMonitor.performHealthCheck();
      
      // And: Third failure occurs
      // When: performHealthCheck() is called
      await apiConnectionMonitor.performHealthCheck();

      // Then: Connection status should be 'disconnected'
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
      
      // And: Consecutive failures should be 3
      const health = apiConnectionMonitor.getHealth();
      expect(health.consecutiveFailures).toBe(3);
    });

    it('should emit degraded event when reliability drops', async () => {
      // Scenario: Reliability drops below threshold
      // Given: 5 successful, 3 failed requests (62.5% reliability)
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // Perform 5 successful checks
      for (let i = 0; i < 5; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // Now start failing
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // Perform 3 failed checks
      for (let i = 0; i < 3; i++) {
        await apiConnectionMonitor.performHealthCheck();
      }

      // When: performHealthCheck() is called
      // Then: Connection status should be 'degraded'
      expect(apiConnectionMonitor.getStatus()).toBe('degraded');
      
      // And: Reliability should be 62.5%
      expect(apiConnectionMonitor.getReliability()).toBeCloseTo(62.5, 1);
    });
  });

  describe('Error Handling', () => {
    it('should handle network errors gracefully', async () => {
      // Scenario: Network error during health check
      // Given: HealthCheckAdapter throws ECONNREFUSED
      mockFetch.mockRejectedValue(new Error('ECONNREFUSED'));

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Should not throw unhandled error
      expect(result).toBeDefined();
      
      // And: Should record failure
      expect(result.healthy).toBe(false);
      expect(result.error).toBeDefined();
      
      // And: Should maintain connection status
      expect(apiConnectionMonitor.getStatus()).toBe('disconnected');
    });

    it('should handle malformed response from health endpoint', async () => {
      // Scenario: Health endpoint returns invalid JSON
      // Given: HealthCheckAdapter returns malformed response
      mockFetch.mockResolvedValue({
        ok: true,
        status: 200,
      });

      // When: performHealthCheck() is called
      const result = await apiConnectionMonitor.performHealthCheck();

      // Then: Should handle parsing error
      // Note: ApiConnectionMonitor doesn't parse JSON, it just checks response.ok
      // So this should succeed
      expect(result.healthy).toBe(true);
      
      // And: Should record as successful check
      const health = apiConnectionMonitor.getHealth();
      expect(health.successfulRequests).toBe(1);
    });

    it('should handle concurrent health check calls', async () => {
      // Scenario: Multiple simultaneous health checks
      // Given: performHealthCheck() is already running
      let resolveFirst: (value: Response) => void;
      const firstPromise = new Promise<Response>((resolve) => {
        resolveFirst = resolve;
      });

      mockFetch.mockImplementation(() => firstPromise);

      // Start first health check
      const firstCheck = apiConnectionMonitor.performHealthCheck();

      // When: performHealthCheck() is called again
      const secondCheck = apiConnectionMonitor.performHealthCheck();

      // Resolve the first check
      resolveFirst!({
        ok: true,
        status: 200,
      } as Response);

      // Wait for both checks to complete
      const [result1, result2] = await Promise.all([firstCheck, secondCheck]);

      // Then: Should return existing check result
      // Note: The second check should return immediately with an error
      // because isChecking is true
      expect(result2.healthy).toBe(false);
      expect(result2.error).toContain('Check already in progress');
    });
  });
});