import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { HealthRouteService } from '@/lib/services/health/HealthRouteService';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { isProductionEnvironment } from '@/lib/config/env';

// Mock the dependencies
vi.mock('@/lib/config/apiBaseUrl', () => ({
  getWebsiteApiBaseUrl: () => 'https://api.example.com',
}));

vi.mock('@/lib/config/env', () => ({
  isProductionEnvironment: () => false,
}));

describe('HealthRouteService', () => {
  let service: HealthRouteService;
  let originalFetch: typeof global.fetch;
  let mockFetch: any;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new HealthRouteService();
    originalFetch = global.fetch;
    mockFetch = vi.fn();
    global.fetch = mockFetch as any;
  });

  afterEach(() => {
    global.fetch = originalFetch;
  });

  describe('happy paths', () => {
    it('should return ok status with timestamp when all dependencies are healthy', async () => {
      // Mock successful responses for all dependencies
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      // Mock database and external service to be healthy
      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('healthy');
      expect(health.timestamp).toBeDefined();
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
      expect(health.dependencies.externalService.status).toBe('healthy');
    });

    it('should return degraded status when external service is slow', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1500,
        error: 'High latency',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('degraded');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });
  });

  describe('failure modes', () => {
    it('should handle API server errors gracefully', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 500,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('unhealthy');
      expect(health.dependencies.api.error).toContain('500');
    });

    it('should handle network errors gracefully', async () => {
      mockFetch.mockRejectedValueOnce(new Error('Network connection failed'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('unhealthy');
      expect(health.dependencies.api.error).toContain('Network connection failed');
    });

    it('should handle database connection failures', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'unhealthy',
        latency: 100,
        error: 'Connection timeout',
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.database.status).toBe('unhealthy');
    });

    it('should handle external service failures gracefully', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 200,
        error: 'Service unavailable',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('degraded');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });

    it('should handle all dependencies failing', async () => {
      mockFetch.mockRejectedValueOnce(new Error('API unavailable'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'unhealthy',
        latency: 100,
        error: 'DB connection failed',
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 150,
        error: 'External service timeout',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('unhealthy');
      expect(health.dependencies.database.status).toBe('unhealthy');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });
  });

  describe('retries', () => {
    it('should retry on transient API failures', async () => {
      // First call fails, second succeeds
      mockFetch
        .mockRejectedValueOnce(new Error('Network timeout'))
        .mockResolvedValueOnce({
          ok: true,
          status: 200,
        });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('healthy');
      expect(mockFetch).toHaveBeenCalledTimes(2);
    });

    it('should retry database health check on transient failures', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      // Mock database to fail first, then succeed
      const checkDatabaseHealthSpy = vi.spyOn(service as any, 'checkDatabaseHealth');
      checkDatabaseHealthSpy
        .mockRejectedValueOnce(new Error('Connection timeout'))
        .mockResolvedValueOnce({
          status: 'healthy',
          latency: 50,
        });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
    });

    it('should exhaust retries and return unhealthy after max attempts', async () => {
      // Mock all retries to fail
      mockFetch.mockRejectedValue(new Error('Persistent network error'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(mockFetch).toHaveBeenCalledTimes(3); // Max retries
    });

    it('should handle mixed retry scenarios', async () => {
      // API succeeds on second attempt
      mockFetch
        .mockRejectedValueOnce(new Error('Timeout'))
        .mockResolvedValueOnce({
          ok: true,
          status: 200,
        });

      // Database fails all attempts
      const checkDatabaseHealthSpy = vi.spyOn(service as any, 'checkDatabaseHealth');
      checkDatabaseHealthSpy.mockRejectedValue(new Error('DB connection failed'));

      // External service succeeds
      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy'); // Database failure makes overall unhealthy
      expect(mockFetch).toHaveBeenCalledTimes(2); // API retried once
      expect(checkDatabaseHealthSpy).toHaveBeenCalledTimes(3); // Database retried max times
    });
  });

  describe('fallback logic', () => {
    it('should continue with degraded status when external service fails', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 2000,
        error: 'External service timeout',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('degraded');
      expect(health.dependencies.externalService.status).toBe('degraded');
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
    });

    it('should handle partial failures without complete system failure', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1500,
        error: 'High latency',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      // System should be degraded but not completely down
      expect(health.status).toBe('degraded');
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
    });

    it('should provide fallback information in details', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1200,
        error: 'External service degraded',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.dependencies.externalService.error).toBe('External service degraded');
    });
  });

  describe('aggregation logic', () => {
    it('should aggregate health status from multiple dependencies correctly', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 45,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 95,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // Verify all dependencies are checked
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
      expect(health.dependencies.externalService.status).toBe('healthy');
      
      // Verify latency aggregation (max of all latencies)
      expect(health.dependencies.api.latency).toBeGreaterThan(0);
      expect(health.dependencies.database.latency).toBeGreaterThan(0);
      expect(health.dependencies.externalService.latency).toBeGreaterThan(0);
    });

    it('should correctly aggregate when one dependency is degraded', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1500,
        error: 'Slow response',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // Aggregation should result in degraded status
      expect(health.status).toBe('degraded');
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('healthy');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });

    it('should handle critical dependency failures in aggregation', async () => {
      mockFetch.mockRejectedValueOnce(new Error('API down'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // API failure should make overall status unhealthy
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('unhealthy');
    });

    it('should aggregate latency values correctly', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 150,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 200,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // Should take the maximum latency
      expect(health.dependencies.api.latency).toBeGreaterThan(0);
      expect(health.dependencies.database.latency).toBe(150);
      expect(health.dependencies.externalService.latency).toBe(200);
    });
  });

  describe('decision branches', () => {
    it('should return healthy when all dependencies are healthy and fast', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().status).toBe('healthy');
    });

    it('should return degraded when dependencies are healthy but slow', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 1200, // Exceeds threshold
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().status).toBe('degraded');
    });

    it('should return unhealthy when critical dependencies fail', async () => {
      mockFetch.mockRejectedValueOnce(new Error('API unavailable'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap().status).toBe('unhealthy');
    });

    it('should handle different error types based on retryability', async () => {
      // Test retryable error (timeout)
      mockFetch.mockRejectedValueOnce(new Error('Connection timeout'));

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result1 = await service.getHealth();
      expect(result1.isOk()).toBe(true);
      expect(mockFetch).toHaveBeenCalledTimes(2); // Should retry

      // Reset mocks
      mockFetch.mockClear();
      vi.clearAllMocks();

      // Test non-retryable error (400)
      mockFetch.mockResolvedValueOnce({
        ok: false,
        status: 400,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result2 = await service.getHealth();
      expect(result2.isOk()).toBe(true);
      expect(mockFetch).toHaveBeenCalledTimes(1); // Should not retry
    });

    it('should handle mixed dependency states correctly', async () => {
      // API: healthy, Database: unhealthy, External: degraded
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'unhealthy',
        latency: 100,
        error: 'DB connection failed',
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1500,
        error: 'Slow response',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // Database failure should make overall unhealthy
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('unhealthy');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });

    it('should handle edge case where all dependencies are degraded', async () => {
      mockFetch.mockResolvedValueOnce({
        ok: true,
        status: 200,
      });

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'degraded',
        latency: 800,
        error: 'Slow query',
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'degraded',
        latency: 1200,
        error: 'External timeout',
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      
      // All degraded should result in degraded overall
      expect(health.status).toBe('degraded');
      expect(health.dependencies.api.status).toBe('healthy');
      expect(health.dependencies.database.status).toBe('degraded');
      expect(health.dependencies.externalService.status).toBe('degraded');
    });

    it('should handle timeout aborts correctly', async () => {
      // Mock fetch to simulate timeout
      const abortError = new Error('The operation was aborted.');
      abortError.name = 'AbortError';
      mockFetch.mockRejectedValueOnce(abortError);

      vi.spyOn(service as any, 'checkDatabaseHealth').mockResolvedValue({
        status: 'healthy',
        latency: 50,
      });

      vi.spyOn(service as any, 'checkExternalServiceHealth').mockResolvedValue({
        status: 'healthy',
        latency: 100,
      });

      const result = await service.getHealth();

      expect(result.isOk()).toBe(true);
      const health = result.unwrap();
      expect(health.status).toBe('unhealthy');
      expect(health.dependencies.api.status).toBe('unhealthy');
    });
  });
});
