/**
 * CircuitBreakerService - P6 熔断器封装.
 *
 * 仲裁依据:
 *   - workline §5.5 P6.1 (熔断器 opossum 完善)
 *   - coord-final-decisions §1 G11 (熔断器: opossum, 50% 阈值, 30s reset)
 *   - president-final-rulings §2.6 (降级模式方案 B: 熔断开启时返回 degraded)
 *
 * 设计:
 *   - 每个下游服务一个独立 CircuitBreaker 实例 (Map 缓存)
 *   - 熔断开启时抛出 ServiceUnavailableError (503)
 *   - 状态变更同步到 student_bff_circuit_state 指标
 *   - half-open 状态允许单次试探调用
 *
 * Opossum 配置 (G11):
 *   - timeout: 5000ms (对齐 DOWNSTREAM_TIMEOUT_MS)
 *   - errorThresholdPercentage: 50%
 *   - resetTimeout: 30000ms (30s 后 half-open)
 *   - volumeThreshold: 10 (最少 10 次调用才评估)
 *   - rollingCountTimeout: 60000 (1 分钟滚动窗口)
 */
import { Injectable, Optional } from "@nestjs/common";
import CircuitBreaker from "opossum";
import promClient from "prom-client";
import type { DownstreamClient, CallOptions } from "@edu/shared-ts/bff";
import { ServiceUnavailableError } from "../errors/application-error.js";
import { metricsRegistry } from "../observability/metrics.js";
import { logger } from "../observability/logger.js";
import { env } from "../../config/env.js";

/**
 * 熔断器状态 (字符串字面量, 对齐 opossum 事件名).
 */
type BreakerState = "closed" | "open" | "half_open";

/**
 * 熔断器配置.
 */
interface BreakerConfig {
  timeoutMs: number;
  errorThresholdPercentage: number;
  resetTimeoutMs: number;
  volumeThreshold: number;
  rollingCountTimeoutMs: number;
}

/**
 * 默认熔断配置 (G11).
 */
const DEFAULT_CONFIG: BreakerConfig = {
  timeoutMs: env.DOWNSTREAM_TIMEOUT_MS,
  errorThresholdPercentage: 50,
  resetTimeoutMs: 30000,
  volumeThreshold: 10,
  rollingCountTimeoutMs: 60000,
};

/**
 * 熔断器状态映射到指标值.
 */
function stateToMetricValue(state: BreakerState): number {
  switch (state) {
    case "closed":
      return 0;
    case "open":
      return 1;
    case "half_open":
      return 2;
    default:
      return 0;
  }
}

/**
 * 熔断器状态名称.
 */
function stateName(state: BreakerState): string {
  switch (state) {
    case "closed":
      return "closed";
    case "open":
      return "open";
    case "half_open":
      return "half_open";
    default:
      return "unknown";
  }
}

@Injectable()
export class CircuitBreakerService {
  private readonly breakers = new Map<string, CircuitBreaker>();
  private readonly config: BreakerConfig;

  constructor(@Optional() config?: Partial<BreakerConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  /**
   * 通过熔断器调用下游服务.
   *
   * @param downstream DownstreamClient 实例
   * @param service 下游服务名
   * @param method RPC 方法名
   * @param request 请求 message
   * @param options 调用配置
   * @returns 下游响应
   * @throws ServiceUnavailableError 当熔断器开启时
   */
  async call<TRequest, TResponse>(
    downstream: DownstreamClient,
    service: string,
    method: string,
    request: TRequest,
    options?: CallOptions,
  ): Promise<TResponse> {
    const breaker = this.getOrCreateBreaker(
      service,
      downstream,
      method,
      request,
      options,
    );

    try {
      return (await breaker.fire()) as TResponse;
    } catch (err) {
      if (err instanceof ServiceUnavailableError) {
        throw err;
      }
      // 重新抛出原始错误 (DownstreamError 等)
      throw err;
    }
  }

  /**
   * 获取熔断器当前状态.
   */
  getState(service: string): BreakerState | null {
    const breaker = this.breakers.get(service);
    if (!breaker) return null;
    if (breaker.opened) return "open";
    if (breaker.halfOpen) return "half_open";
    return "closed";
  }

  /**
   * 获取或创建某服务的熔断器.
   *
   * 注意: 由于 opossum 的 fire() 不接受动态参数, 我们在每次调用时
   * 通过闭包捕获当前的 method/request/options.
   * 实际上 opossum 支持 fire(args...), 但此处为简化设计,
   * 每次创建新的执行函数.
   *
   * 为避免创建过多 breaker 实例, 我们按 service 名缓存 breaker,
   * 并在 fire 前更新其执行函数.
   */
  private getOrCreateBreaker<TRequest>(
    service: string,
    downstream: DownstreamClient,
    method: string,
    request: TRequest,
    options?: CallOptions,
  ): CircuitBreaker {
    let breaker = this.breakers.get(service);

    if (!breaker) {
      const execFn = async (): Promise<unknown> => {
        return downstream.call(service, method, request, options);
      };

      breaker = new CircuitBreaker(execFn, {
        timeout: this.config.timeoutMs,
        errorThresholdPercentage: this.config.errorThresholdPercentage,
        resetTimeout: this.config.resetTimeoutMs,
        volumeThreshold: this.config.volumeThreshold,
        rollingCountTimeout: this.config.rollingCountTimeoutMs,
      });

      // 状态变更监听
      breaker.on("open", () => {
        logger.warn({ service }, "Circuit breaker OPENED");
        this.updateMetric(service, "open");
      });

      breaker.on("close", () => {
        logger.info({ service }, "Circuit breaker CLOSED (recovered)");
        this.updateMetric(service, "closed");
      });

      breaker.on("halfOpen", () => {
        logger.info({ service }, "Circuit breaker HALF-OPEN");
        this.updateMetric(service, "half_open");
      });

      // fallback: 熔断开启时返回 ServiceUnavailableError
      breaker.fallback(() => {
        throw new ServiceUnavailableError(
          `Circuit breaker open for service: ${service}`,
          { service, state: "open" },
        );
      });

      this.breakers.set(service, breaker);
      this.updateMetric(service, "closed");
    } else {
      // 更新执行函数 (opossum 允许重新设置 action)
      // 由于 opossum 不支持直接替换 action, 我们使用 wrapper 方式
      // 实际上 opossum 的 fire() 会调用构造时传入的函数,
      // 所以我们用一个可变的 wrapper
      (breaker as unknown as { action: () => Promise<unknown> }).action =
        async () => {
          return downstream.call(service, method, request, options);
        };
    }

    return breaker;
  }

  /**
   * 更新熔断器指标.
   */
  private updateMetric(service: string, state: BreakerState): void {
    const value = stateToMetricValue(state);
    const name = stateName(state);
    (
      metricsRegistry.getSingleMetric("student_bff_circuit_state") as
        promClient.Gauge<string> | undefined
    )?.set({ service, state: name }, value);
  }

  /**
   * 关闭所有熔断器 (优雅关闭).
   */
  async shutdown(): Promise<void> {
    for (const [service, breaker] of this.breakers) {
      breaker.shutdown();
      logger.debug({ service }, "Circuit breaker shut down");
    }
    this.breakers.clear();
  }
}
