/**
 * Integration Tests for Health Check Routes
 *
 * Tests the health, readiness, and liveness endpoints
 */

// Mock queue BEFORE any imports to prevent Redis connection
jest.mock('../../../queue', () => ({
  translationQueue: {
    add: jest.fn().mockResolvedValue({ id: 'mock-job-id' }),
    process: jest.fn(),
    on: jest.fn(),
    close: jest.fn().mockResolvedValue(undefined),
    getJob: jest.fn().mockResolvedValue(null),
    getJobs: jest.fn().mockResolvedValue([]),
    pause: jest.fn().mockResolvedValue(undefined),
    resume: jest.fn().mockResolvedValue(undefined),
    clean: jest.fn().mockResolvedValue([]),
    empty: jest.fn().mockResolvedValue(undefined),
  },
}));

import request from 'supertest';
import { createServer } from '../../../server';
import { prismaMock, mockRedisClient } from '../../setup';
import { Application } from 'express';

describe('Health Routes', () => {
  let app: Application;

  beforeAll(() => {
    app = createServer();
  });

  describe('GET /health', () => {
    it('should return healthy status', async () => {
      const response = await request(app)
        .get('/health')
        .expect(200);

      expect(response.body).toMatchObject({
        status: 'healthy',
        service: 'translate-api',
        version: '1.0.0',
        capabilities: {
          siteContentSegmentedTranslation: {
            version: 1,
            resourceType: 'site_content',
            maxPayloadBytes: 1_048_576,
            maxSegments: 1_000,
            maxCharacters: 50_000,
          },
        },
      });
      expect(response.body.timestamp).toBeDefined();
    });

    it('should have valid timestamp format', async () => {
      const response = await request(app)
        .get('/health')
        .expect(200);

      const timestamp = new Date(response.body.timestamp);
      expect(timestamp.toString()).not.toBe('Invalid Date');
    });
  });

  describe('GET /health/ready', () => {
    it('should return ready when all services are healthy', async () => {
      // Mock successful database query
      prismaMock.$queryRaw.mockResolvedValue([{ result: 1 }]);

      // Mock successful Redis ping
      mockRedisClient.ping.mockResolvedValue('PONG');
      mockRedisClient.quit.mockResolvedValue('OK');

      const response = await request(app)
        .get('/health/ready')
        .expect(200);

      expect(response.body).toMatchObject({
        status: 'ready',
        checks: {
          database: { status: 'healthy' },
          redis: { status: 'healthy' },
        },
      });
      expect(response.body.timestamp).toBeDefined();
    });

    it('should return 503 when database is unhealthy', async () => {
      // Mock database failure
      prismaMock.$queryRaw.mockRejectedValue(new Error('Connection refused'));

      // Mock successful Redis
      mockRedisClient.ping.mockResolvedValue('PONG');
      mockRedisClient.quit.mockResolvedValue('OK');

      const response = await request(app)
        .get('/health/ready')
        .expect(503);

      expect(response.body.status).toBe('not ready');
      expect(response.body.checks.database.status).toBe('unhealthy');
      expect(response.body.checks.database.error).toBeDefined();
    });

    it('should return 503 when Redis is unhealthy', async () => {
      // Mock successful database
      prismaMock.$queryRaw.mockResolvedValue([{ result: 1 }]);

      // Mock Redis failure
      mockRedisClient.ping.mockRejectedValue(new Error('Connection timeout'));

      const response = await request(app)
        .get('/health/ready')
        .expect(503);

      expect(response.body.status).toBe('not ready');
      expect(response.body.checks.redis.status).toBe('unhealthy');
      expect(response.body.checks.redis.error).toBeDefined();
    });

    it('should return 503 when both services are unhealthy', async () => {
      // Mock both failures
      prismaMock.$queryRaw.mockRejectedValue(new Error('DB error'));
      mockRedisClient.ping.mockRejectedValue(new Error('Redis error'));

      const response = await request(app)
        .get('/health/ready')
        .expect(503);

      expect(response.body.status).toBe('not ready');
      expect(response.body.checks.database.status).toBe('unhealthy');
      expect(response.body.checks.redis.status).toBe('unhealthy');
    });
  });

  describe('GET /health/live', () => {
    it('should return alive status', async () => {
      const response = await request(app)
        .get('/health/live')
        .expect(200);

      expect(response.body).toMatchObject({
        status: 'alive',
      });
      expect(response.body.uptime).toBeDefined();
      expect(response.body.memory).toBeDefined();
      expect(response.body.timestamp).toBeDefined();
    });

    it('should include process uptime', async () => {
      const response = await request(app)
        .get('/health/live')
        .expect(200);

      expect(typeof response.body.uptime).toBe('number');
      expect(response.body.uptime).toBeGreaterThanOrEqual(0);
    });

    it('should include memory usage', async () => {
      const response = await request(app)
        .get('/health/live')
        .expect(200);

      expect(response.body.memory).toBeDefined();
      expect(response.body.memory.heapUsed).toBeDefined();
      expect(response.body.memory.heapTotal).toBeDefined();
      expect(response.body.memory.rss).toBeDefined();
      expect(typeof response.body.memory.heapUsed).toBe('number');
    });

    it('should always return 200 even if other services are down', async () => {
      // Mock service failures
      prismaMock.$queryRaw.mockRejectedValue(new Error('DB error'));
      mockRedisClient.ping.mockRejectedValue(new Error('Redis error'));

      // Liveness should still return 200
      const response = await request(app)
        .get('/health/live')
        .expect(200);

      expect(response.body.status).toBe('alive');
    });
  });

  describe('Health endpoint consistency', () => {
    it('should have consistent timestamp format across all endpoints', async () => {
      const health = await request(app).get('/health');
      const ready = await request(app).get('/health/ready');
      const live = await request(app).get('/health/live');

      const timestamps = [
        health.body.timestamp,
        ready.body.timestamp,
        live.body.timestamp,
      ];

      timestamps.forEach((timestamp) => {
        expect(new Date(timestamp).toString()).not.toBe('Invalid Date');
      });
    });

    it('should respond quickly (< 1 second)', async () => {
      const start = Date.now();

      await request(app).get('/health');

      const duration = Date.now() - start;
      expect(duration).toBeLessThan(1000);
    });
  });
});
