import { type HealthCheckService } from '@nestjs/terminus';
import { HealthController } from '../health.controller';
import { type PrismaHealthIndicator } from '../infrastructure/prisma.health';
import { type RedisHealthIndicator } from '../infrastructure/redis.health';

describe('HealthController', () => {
  let controller: HealthController;
  let mockHealthCheckService: { check: ReturnType<typeof vi.fn> };
  let mockPrismaHealth: PrismaHealthIndicator;
  let mockRedisHealth: RedisHealthIndicator;

  beforeEach(() => {
    mockHealthCheckService = { check: vi.fn() };
    mockPrismaHealth = { isHealthy: vi.fn() } as unknown as PrismaHealthIndicator;
    mockRedisHealth = { isHealthy: vi.fn() } as unknown as RedisHealthIndicator;

    controller = new HealthController(
      mockHealthCheckService as unknown as HealthCheckService,
      mockPrismaHealth,
      mockRedisHealth,
    );
  });

  describe('liveness (GET /health)', () => {
    it('calls health.check with an empty array', () => {
      const expected = { status: 'ok', details: {} };
      mockHealthCheckService.check.mockReturnValue(expected);

      const result = controller.liveness();

      expect(mockHealthCheckService.check).toHaveBeenCalledOnce();
      expect(mockHealthCheckService.check).toHaveBeenCalledWith([]);
      expect(result).toBe(expected);
    });
  });

  describe('readiness (GET /health/ready)', () => {
    it('calls health.check with prisma and redis health indicators', async () => {
      const expected = {
        status: 'ok',
        details: { database: { status: 'up' }, redis: { status: 'up' } },
      };
      mockHealthCheckService.check.mockImplementation(
        async (indicators: Array<() => Promise<unknown>>) => {
          // Execute the indicator callbacks to verify they call the right methods
          for (const indicator of indicators) {
            await indicator();
          }
          return expected;
        },
      );
      (mockPrismaHealth.isHealthy as ReturnType<typeof vi.fn>).mockResolvedValue({
        database: { status: 'up' },
      });
      (mockRedisHealth.isHealthy as ReturnType<typeof vi.fn>).mockResolvedValue({
        redis: { status: 'up' },
      });

      const result = await controller.readiness();

      expect(result).toBe(expected);
      expect(mockPrismaHealth.isHealthy).toHaveBeenCalledWith('database');
      expect(mockRedisHealth.isHealthy).toHaveBeenCalledWith('redis');
    });

    it('passes two health indicator callbacks to health.check', () => {
      mockHealthCheckService.check.mockReturnValue({ status: 'ok' });

      controller.readiness();

      const callbacks = mockHealthCheckService.check.mock.calls[0][0];
      expect(callbacks).toHaveLength(2);
      expect(typeof callbacks[0]).toBe('function');
      expect(typeof callbacks[1]).toBe('function');
    });
  });

  describe('database (GET /health/db)', () => {
    it('calls health.check with only the prisma health indicator', async () => {
      const expected = {
        status: 'ok',
        details: { database: { status: 'up' } },
      };
      mockHealthCheckService.check.mockImplementation(
        async (indicators: Array<() => Promise<unknown>>) => {
          for (const indicator of indicators) {
            await indicator();
          }
          return expected;
        },
      );
      (mockPrismaHealth.isHealthy as ReturnType<typeof vi.fn>).mockResolvedValue({
        database: { status: 'up' },
      });

      const result = await controller.database();

      expect(result).toBe(expected);
      expect(mockPrismaHealth.isHealthy).toHaveBeenCalledWith('database');
      expect(mockRedisHealth.isHealthy).not.toHaveBeenCalled();
    });

    it('passes one health indicator callback to health.check', () => {
      mockHealthCheckService.check.mockReturnValue({ status: 'ok' });

      controller.database();

      const callbacks = mockHealthCheckService.check.mock.calls[0][0];
      expect(callbacks).toHaveLength(1);
      expect(typeof callbacks[0]).toBe('function');
    });
  });

  describe('redis (GET /health/redis)', () => {
    it('calls health.check with only the redis health indicator', async () => {
      const expected = {
        status: 'ok',
        details: { redis: { status: 'up' } },
      };
      mockHealthCheckService.check.mockImplementation(
        async (indicators: Array<() => Promise<unknown>>) => {
          for (const indicator of indicators) {
            await indicator();
          }
          return expected;
        },
      );
      (mockRedisHealth.isHealthy as ReturnType<typeof vi.fn>).mockResolvedValue({
        redis: { status: 'up' },
      });

      const result = await controller.redis();

      expect(result).toBe(expected);
      expect(mockRedisHealth.isHealthy).toHaveBeenCalledWith('redis');
      expect(mockPrismaHealth.isHealthy).not.toHaveBeenCalled();
    });

    it('passes one health indicator callback to health.check', () => {
      mockHealthCheckService.check.mockReturnValue({ status: 'ok' });

      controller.redis();

      const callbacks = mockHealthCheckService.check.mock.calls[0][0];
      expect(callbacks).toHaveLength(1);
      expect(typeof callbacks[0]).toBe('function');
    });
  });
});
