import { Test, TestingModule } from '@nestjs/testing';
import { HealthController } from './health.controller';
import {
  HealthCheckService,
  MemoryHealthIndicator,
  DiskHealthIndicator,
  HealthCheckResult,
} from '@nestjs/terminus';
import { PrismaHealthIndicator } from './prisma-health.indicator';
import { ModulesHealthIndicator } from './modules-health.indicator';

describe('HealthController', () => {
  let controller: HealthController;
  let healthCheckService: HealthCheckService;

  const mockHealthCheckService = {
    check: jest.fn(),
  };

  const mockMemoryHealth = {
    checkHeap: jest.fn(),
    checkRSS: jest.fn(),
  };

  const mockDiskHealth = {
    checkStorage: jest.fn(),
  };

  const mockPrismaHealth = {
    isHealthy: jest.fn(),
  };

  const mockModulesHealth = {
    isHealthy: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      controllers: [HealthController],
      providers: [
        { provide: HealthCheckService, useValue: mockHealthCheckService },
        { provide: MemoryHealthIndicator, useValue: mockMemoryHealth },
        { provide: DiskHealthIndicator, useValue: mockDiskHealth },
        { provide: PrismaHealthIndicator, useValue: mockPrismaHealth },
        { provide: ModulesHealthIndicator, useValue: mockModulesHealth },
      ],
    }).compile();

    controller = module.get<HealthController>(HealthController);
    healthCheckService = module.get<HealthCheckService>(HealthCheckService);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should be defined', () => {
    expect(controller).toBeDefined();
  });

  describe('check()', () => {
    it('should call health check service with all indicators', async () => {
      const expectedResult: HealthCheckResult = {
        status: 'ok',
        info: {
          database: { status: 'up' },
          memory_heap: { status: 'up' },
          memory_rss: { status: 'up' },
          modules: { status: 'up' },
        },
        error: {},
        details: {
          database: { status: 'up' },
          memory_heap: { status: 'up' },
          memory_rss: { status: 'up' },
          modules: { status: 'up' },
        },
      };

      mockHealthCheckService.check.mockResolvedValue(expectedResult);

      const result = await controller.check();
      expect(result).toEqual(expectedResult);
      expect(mockHealthCheckService.check).toHaveBeenCalledWith(
        expect.arrayContaining([
          expect.any(Function),
          expect.any(Function),
          expect.any(Function),
          expect.any(Function),
        ]),
      );
    });

    it('should pass an array of 4 health indicator callbacks', async () => {
      mockHealthCheckService.check.mockResolvedValue({ status: 'ok' });

      await controller.check();
      const checkArgs = mockHealthCheckService.check.mock.calls[0][0];
      expect(checkArgs).toHaveLength(4);
    });
  });

  describe('liveness()', () => {
    it('should return ok status with timestamp', () => {
      const result = controller.liveness();
      expect(result).toHaveProperty('status', 'ok');
      expect(result).toHaveProperty('timestamp');
      expect(typeof result.timestamp).toBe('string');
    });

    it('should return a valid ISO timestamp', () => {
      const result = controller.liveness();
      const parsed = new Date(result.timestamp);
      expect(parsed.toISOString()).toBe(result.timestamp);
    });
  });

  describe('readiness()', () => {
    it('should call health check service with database check only', async () => {
      const expectedResult: HealthCheckResult = {
        status: 'ok',
        info: { database: { status: 'up' } },
        error: {},
        details: { database: { status: 'up' } },
      };

      mockHealthCheckService.check.mockResolvedValue(expectedResult);

      const result = await controller.readiness();
      expect(result).toEqual(expectedResult);
      expect(mockHealthCheckService.check).toHaveBeenCalledWith(
        expect.arrayContaining([expect.any(Function)]),
      );
    });

    it('should pass exactly 1 health indicator callback for readiness', async () => {
      mockHealthCheckService.check.mockResolvedValue({ status: 'ok' });

      await controller.readiness();
      const checkArgs = mockHealthCheckService.check.mock.calls[0][0];
      expect(checkArgs).toHaveLength(1);
    });
  });
});
