import { describe, it, expect, beforeEach, vi } from "vitest";
import type { IamClient } from "../../src/clients/iam.client.js";
import type { CoreEduClient } from "../../src/clients/core-edu.client.js";
import type { DataAnaClient } from "../../src/clients/data-ana.client.js";
import type {
  UserInfoDto,
  GradeDto,
  StudentWeaknessDto,
  ClassInfoDto,
  HomeworkDto,
  ExamDto,
  LearningTrendDto,
  ClassPerformanceDto,
} from "../../src/clients/dtos.js";

// Mock safeRedis 以控制 Redis 探针行为
vi.mock("../../src/shared/cache/redis.client.js", () => ({
  safeRedis: vi.fn().mockResolvedValue("PONG"),
  getRedisClient: vi.fn(() => null),
  closeRedisClient: vi.fn(),
}));

import { HealthController } from "../../src/shared/health/health.controller.js";
import { safeRedis } from "../../src/shared/cache/redis.client.js";

const mockSafeRedis = vi.mocked(safeRedis);

class UpIamClient implements IamClient {
  async getUserInfo(userId: string): Promise<UserInfoDto> {
    return {
      id: userId,
      email: "",
      name: "",
      roles: [],
      permissions: [],
    };
  }
  async getChildrenByParent(_parentId: string) {
    return [];
  }
  async getViewports(_userId: string) {
    return [];
  }
  async getEffectivePermissions(_userId: string) {
    return [];
  }
}

class DownIamClient implements IamClient {
  async getUserInfo(_userId: string): Promise<UserInfoDto> {
    throw new Error("iam gRPC unavailable");
  }
  async getChildrenByParent(_parentId: string) {
    return [];
  }
  async getViewports(_userId: string) {
    return [];
  }
  async getEffectivePermissions(_userId: string) {
    return [];
  }
}

class UpCoreEduClient implements CoreEduClient {
  async listGradesByStudent(_studentId: string): Promise<GradeDto[]> {
    return [];
  }
  async listHomeworkByClass(_classId: string): Promise<HomeworkDto[]> {
    return [];
  }
  async listExamsByClass(_classId: string): Promise<ExamDto[]> {
    return [];
  }
  async getClass(classId: string): Promise<ClassInfoDto> {
    return { id: classId, name: "", gradeId: "" };
  }
}

class DownCoreEduClient implements CoreEduClient {
  async listGradesByStudent(_studentId: string): Promise<GradeDto[]> {
    throw new Error("core-edu gRPC unavailable");
  }
  async listHomeworkByClass(_classId: string): Promise<HomeworkDto[]> {
    return [];
  }
  async listExamsByClass(_classId: string): Promise<ExamDto[]> {
    return [];
  }
  async getClass(classId: string): Promise<ClassInfoDto> {
    return { id: classId, name: "", gradeId: "" };
  }
}

class UpDataAnaClient implements DataAnaClient {
  async getStudentWeakness(
    studentId: string,
    _subjectId: string,
  ): Promise<StudentWeaknessDto> {
    return { studentId, weakPoints: [] };
  }
  async getLearningTrend(
    studentId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<LearningTrendDto> {
    return { studentId, points: [] };
  }
  async getClassPerformance(
    classId: string,
    _subjectId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<ClassPerformanceDto> {
    return { classId, averageScore: 0, passRate: 0, scores: [] };
  }
}

class DownDataAnaClient implements DataAnaClient {
  async getStudentWeakness(
    _studentId: string,
    _subjectId: string,
  ): Promise<StudentWeaknessDto> {
    throw new Error("data-ana gRPC unavailable");
  }
  async getLearningTrend(
    studentId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<LearningTrendDto> {
    return { studentId, points: [] };
  }
  async getClassPerformance(
    classId: string,
    _subjectId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<ClassPerformanceDto> {
    return { classId, averageScore: 0, passRate: 0, scores: [] };
  }
}

describe("测试用例 9：/readyz 下游探针（Integration）", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    mockSafeRedis.mockResolvedValue("PONG");
  });

  describe("/healthz（liveness）", () => {
    it("返回 status=ok + service 名 + timestamp", () => {
      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = controller.liveness();

      expect(result.status).toBe("ok");
      expect(result.service).toBe("parent-bff");
      expect(result.timestamp).toBeTruthy();

      // timestamp 是合法 ISO
      const ts = new Date(result.timestamp);
      expect(Number.isNaN(ts.getTime())).toBe(false);
    });
  });

  describe("/readyz（readiness）全探针正常", () => {
    it("4 项探针全部 up 时返回 status=ready", async () => {
      mockSafeRedis.mockResolvedValue("PONG");

      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("ready");
      expect(result.service).toBe("parent-bff");
      expect(result.checks["iam"]?.status).toBe("up");
      expect(result.checks["core-edu"]?.status).toBe("up");
      expect(result.checks["data-ana"]?.status).toBe("up");
      expect(result.checks["redis"]?.status).toBe("up");
    });
  });

  describe("/readyz iam 故障", () => {
    it("iam 探针失败时返回 status=degraded + iam.status=down", async () => {
      mockSafeRedis.mockResolvedValue("PONG");

      const controller = new HealthController(
        new DownIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["iam"]?.status).toBe("down");
      expect(result.checks["iam"]?.error).toContain("iam gRPC unavailable");
      // 其他探针仍正常
      expect(result.checks["core-edu"]?.status).toBe("up");
      expect(result.checks["data-ana"]?.status).toBe("up");
      expect(result.checks["redis"]?.status).toBe("up");
    });
  });

  describe("/readyz core-edu 故障", () => {
    it("core-edu 探针失败时返回 status=degraded", async () => {
      mockSafeRedis.mockResolvedValue("PONG");

      const controller = new HealthController(
        new UpIamClient(),
        new DownCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["core-edu"]?.status).toBe("down");
      expect(result.checks["core-edu"]?.error).toContain(
        "core-edu gRPC unavailable",
      );
    });
  });

  describe("/readyz data-ana 故障", () => {
    it("data-ana 探针失败时返回 status=degraded", async () => {
      mockSafeRedis.mockResolvedValue("PONG");

      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new DownDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["data-ana"]?.status).toBe("down");
      expect(result.checks["data-ana"]?.error).toContain(
        "data-ana gRPC unavailable",
      );
    });
  });

  describe("/readyz Redis 故障", () => {
    it("Redis 探针失败时返回 status=degraded + redis.status=down", async () => {
      mockSafeRedis.mockRejectedValue(new Error("redis connection refused"));

      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["redis"]?.status).toBe("down");
      expect(result.checks["redis"]?.error).toContain(
        "redis connection refused",
      );
      // 其他探针仍正常
      expect(result.checks["iam"]?.status).toBe("up");
    });

    it("Redis 返回非 PONG 时 status=down", async () => {
      mockSafeRedis.mockResolvedValue("unexpected");

      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["redis"]?.status).toBe("down");
      expect(result.checks["redis"]?.error).toContain(
        "unexpected ping response",
      );
    });
  });

  describe("/readyz 全部故障", () => {
    it("所有探针都失败时 status=degraded", async () => {
      mockSafeRedis.mockRejectedValue(new Error("redis down"));

      const controller = new HealthController(
        new DownIamClient(),
        new DownCoreEduClient(),
        new DownDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(result.status).toBe("degraded");
      expect(result.checks["iam"]?.status).toBe("down");
      expect(result.checks["core-edu"]?.status).toBe("down");
      expect(result.checks["data-ana"]?.status).toBe("down");
      expect(result.checks["redis"]?.status).toBe("down");
    });
  });

  describe("探针延迟记录", () => {
    it("每个探针返回 latency_ms", async () => {
      mockSafeRedis.mockResolvedValue("PONG");

      const controller = new HealthController(
        new UpIamClient(),
        new UpCoreEduClient(),
        new UpDataAnaClient(),
      );

      const result = await controller.readiness();

      expect(typeof result.checks["iam"]?.latency_ms).toBe("number");
      expect(typeof result.checks["core-edu"]?.latency_ms).toBe("number");
      expect(typeof result.checks["data-ana"]?.latency_ms).toBe("number");
      expect(typeof result.checks["redis"]?.latency_ms).toBe("number");
    });
  });
});
