/**
 * Health Controller - /healthz + /readyz 探针.
 *
 * 仲裁依据:
 *   - coord-final-decisions §1 G2 (首次实现即检查全部下游依赖)
 *   - coord-final-decisions §1 G3 (/healthz liveness)
 *   - president-final-rulings §2.4 (/readyz 探针按阶段扩展, 必需失败返回 503, 可选软失败)
 *
 * 探针列表 (按阶段扩展, president §2.4):
 *   P3: Redis + iam gRPC + core-edu gRPC (3 项)
 *   P4: + content gRPC + data-ana gRPC (5 项)
 *   P5: + msg gRPC + ai gRPC (7 项)
 *
 * 实现方式: DownstreamClient.checkHealth() 检查 gRPC 可达性 + CacheService.ping() 检查 Redis.
 */
import { Controller, Get, HttpCode, HttpStatus, Inject } from "@nestjs/common";
import { DownstreamClient } from "@edu/shared-ts/bff";
import { REDIS_CLIENT, CacheService } from "../cache/cache.module.js";
import type { Redis } from "ioredis";
import { downstreamServices } from "../../config/downstream.js";
import { env } from "../../config/env.js";
import { logger } from "../observability/logger.js";

interface HealthCheck {
  service: string;
  healthy: boolean;
  required: boolean;
  latencyMs?: number;
}

interface ReadyzResponse {
  status: "ok" | "degraded" | "unavailable";
  service: string;
  timestamp: string;
  checks: HealthCheck[];
  degraded?: boolean;
  degradedServices?: string[];
}

const SERVICE_NAME = "student-bff";

@Controller()
export class HealthController {
  constructor(
    private readonly downstream: DownstreamClient,
    @Inject(REDIS_CLIENT) private readonly redis: Redis,
  ) {}

  /**
   * /healthz: Liveness probe (G3).
   * 仅检查进程存活, 不检查依赖.
   */
  @Get("healthz")
  @HttpCode(HttpStatus.OK)
  liveness(): { status: string; service: string; timestamp: string } {
    return {
      status: "ok",
      service: SERVICE_NAME,
      timestamp: new Date().toISOString(),
    };
  }

  /**
   * /readyz: Readiness probe (G2 + president §2.4).
   *
   * 检查全部已启用的下游依赖:
   *   - Redis PING
   *   - 各下游 gRPC waitForReady
   *
   * 必需依赖失败 → 503 (触发 Pod 重启)
   * 可选依赖失败 → 200 + degraded=true (软失败)
   */
  @Get("readyz")
  async readiness(): Promise<ReadyzResponse> {
    const checks: HealthCheck[] = [];

    // 1. Redis 检查
    const redisStart = Date.now();
    const cacheService = new CacheService(this.redis);
    const redisOk = await cacheService.ping();
    checks.push({
      service: "redis",
      healthy: redisOk,
      required: true,
      latencyMs: Date.now() - redisStart,
    });

    // 2. 各下游 gRPC 检查 (env.MOCK_UPSTREAM=true 时跳过, 直接 healthy)
    for (const svc of downstreamServices) {
      if (!svc.enabled) continue;
      const start = Date.now();
      let healthy = true;
      if (!env.MOCK_UPSTREAM) {
        healthy = await this.downstream.checkHealth(svc.name);
      }
      checks.push({
        service: svc.name,
        healthy,
        required: svc.required,
        latencyMs: Date.now() - start,
      });
    }

    // 3. 判断整体状态
    const failedRequired = checks.filter((c) => !c.healthy && c.required);
    const failedOptional = checks.filter((c) => !c.healthy && !c.required);

    let status: ReadyzResponse["status"] = "ok";
    let httpStatus = HttpStatus.OK;
    let degraded = false;

    if (failedRequired.length > 0) {
      status = "unavailable";
      httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
      logger.warn(
        { failedRequired: failedRequired.map((c) => c.service) },
        "/readyz failed: required dependencies unavailable",
      );
    } else if (failedOptional.length > 0) {
      status = "degraded";
      degraded = true;
      logger.warn(
        { failedOptional: failedOptional.map((c) => c.service) },
        "/readyz degraded: optional dependencies unavailable",
      );
    }

    const response: ReadyzResponse = {
      status,
      service: SERVICE_NAME,
      timestamp: new Date().toISOString(),
      checks,
      degraded,
      degradedServices: [...failedRequired, ...failedOptional].map((c) => c.service),
    };

    // NestJS 4.x 的 @HttpCode 装饰器对 async 方法不一定生效, 这里通过 throw 切换状态码
    if (httpStatus !== HttpStatus.OK) {
      // 通过抛 HttpException 切换状态码
      const { HttpException } = await import("@nestjs/common");
      throw new HttpException(response, httpStatus);
    }

    return response;
  }
}
