import { describe, it, expect, beforeEach, vi } from "vitest";
import { HttpException, HttpStatus } from "@nestjs/common";
import { HealthController } from "./health.controller";
import { HealthService } from "./health.service";
import { ValkeyService } from "../../valkey/valkey.service";

// Mock ValkeyService
const mockValkeyService = {
  ping: vi.fn(),
} as unknown as ValkeyService;

describe("HealthController", () => {
  let controller: HealthController;
  let service: HealthService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new HealthService(mockValkeyService);
    controller = new HealthController(service);
  });

  describe("GET /health", () => {
    it("should return 200 OK with correct format", () => {
      const result = controller.check();

      expect(result).toBeDefined();
      expect(result).toHaveProperty("status");
      expect(result).toHaveProperty("uptime");
      expect(result).toHaveProperty("timestamp");
    });

    it('should return status as "healthy"', () => {
      const result = controller.check();

      expect(result.status).toBe("healthy");
    });

    it("should return uptime as a positive number", () => {
      const result = controller.check();

      expect(typeof result.uptime).toBe("number");
      expect(result.uptime).toBeGreaterThanOrEqual(0);
    });

    it("should return timestamp as valid ISO 8601 string", () => {
      const result = controller.check();

      expect(typeof result.timestamp).toBe("string");
      expect(() => new Date(result.timestamp)).not.toThrow();

      // Verify it's a valid ISO 8601 format
      const date = new Date(result.timestamp);
      expect(date.toISOString()).toBe(result.timestamp);
    });

    it("should return only required fields (status, uptime, timestamp)", () => {
      const result = controller.check();

      const keys = Object.keys(result);
      expect(keys).toHaveLength(3);
      expect(keys).toContain("status");
      expect(keys).toContain("uptime");
      expect(keys).toContain("timestamp");
    });

    it("should increment uptime over time", async () => {
      const result1 = controller.check();
      const uptime1 = result1.uptime;

      // Wait 1100ms to ensure at least 1 second has passed
      await new Promise((resolve) => setTimeout(resolve, 1100));

      const result2 = controller.check();
      const uptime2 = result2.uptime;

      // Uptime should be at least 1 second higher
      expect(uptime2).toBeGreaterThanOrEqual(uptime1 + 1);
    });

    it("should return current timestamp", () => {
      const before = Date.now();
      const result = controller.check();
      const after = Date.now();

      const resultTime = new Date(result.timestamp).getTime();

      // Timestamp should be between before and after (within test execution time)
      expect(resultTime).toBeGreaterThanOrEqual(before);
      expect(resultTime).toBeLessThanOrEqual(after);
    });
  });

  describe("GET /health/ready", () => {
    it("should return ready status with checks when all dependencies are healthy", async () => {
      vi.mocked(mockValkeyService.ping).mockResolvedValue(true);

      const result = await controller.ready();

      expect(result).toBeDefined();
      expect(result).toHaveProperty("ready");
      expect(result).toHaveProperty("checks");
      expect(result.ready).toBe(true);
      expect(result.checks.valkey).toBe(true);
    });

    it("should throw 503 when Valkey is unhealthy", async () => {
      vi.mocked(mockValkeyService.ping).mockResolvedValue(false);

      await expect(controller.ready()).rejects.toThrow(HttpException);

      try {
        await controller.ready();
      } catch (error) {
        expect(error).toBeInstanceOf(HttpException);
        expect((error as HttpException).getStatus()).toBe(HttpStatus.SERVICE_UNAVAILABLE);
        const response = (error as HttpException).getResponse() as { ready: boolean };
        expect(response.ready).toBe(false);
      }
    });

    it("should return checks object with individual dependency status", async () => {
      vi.mocked(mockValkeyService.ping).mockResolvedValue(true);

      const result = await controller.ready();

      expect(result.checks).toBeDefined();
      expect(typeof result.checks.valkey).toBe("boolean");
    });

    it("should handle Valkey ping errors gracefully", async () => {
      vi.mocked(mockValkeyService.ping).mockRejectedValue(new Error("Connection refused"));

      await expect(controller.ready()).rejects.toThrow(HttpException);
    });
  });
});
