// DownstreamClient 抽象基类（B8 裁决：3 个 BFF 统一使用）
// 提供统一的错误映射 + 结构化日志 + metrics 记录
import type { Logger } from "pino";
import { logger } from "../shared/observability/logger.js";
import {
  BadGatewayError,
  AggregationFailedError,
} from "../shared/errors/application-error.js";
import type {
  CallContext,
  DownstreamError,
  DownstreamResult,
  DownstreamServiceName,
  GrpcMetadata,
} from "./types.js";

/** gRPC 状态码 → HTTP 语义映射（@grpc/grpc-js status codes） */
const GRPC_STATUS = {
  OK: 0,
  CANCELLED: 1,
  UNKNOWN: 2,
  INVALID_ARGUMENT: 3,
  DEADLINE_EXCEEDED: 4,
  NOT_FOUND: 5,
  ALREADY_EXISTS: 6,
  PERMISSION_DENIED: 7,
  RESOURCE_EXHAUSTED: 8,
  FAILED_PRECONDITION: 9,
  ABORTED: 10,
  OUT_OF_RANGE: 11,
  UNIMPLEMENTED: 12,
  INTERNAL: 13,
  UNAVAILABLE: 14,
  DATA_LOSS: 15,
  UNAUTHENTICATED: 16,
} as const;

export abstract class BaseDownstreamClient {
  protected log: Logger;
  abstract readonly serviceName: DownstreamServiceName;

  constructor() {
    // abstract property 在子类构造后才可用，延迟初始化 logger
    this.log = logger.child({ downstream: "downstream" });
  }

  /** 子类在构造函数中调用以初始化 logger（带正确 serviceName） */
  protected initLogger(): void {
    this.log = logger.child({ downstream: this.serviceName });
  }

  /** 构建 gRPC metadata（注入 x-user-id + x-request-id） */
  protected buildMetadata(ctx: CallContext): GrpcMetadata {
    const meta: GrpcMetadata = { "x-user-id": ctx.userId };
    if (ctx.traceId) {
      meta["x-request-id"] = ctx.traceId;
    }
    return meta;
  }

  /** 将 gRPC 错误映射为 BadGatewayError（502） */
  protected mapGrpcError(err: unknown, rpc: string): BadGatewayError {
    const grpcErr = err as {
      code?: number;
      message?: string;
      details?: string;
    };
    const code = grpcErr.code ?? GRPC_STATUS.UNKNOWN;
    const message = grpcErr.message ?? "Unknown gRPC error";

    this.log.warn(
      { rpc, grpcCode: code, message, err },
      "Downstream gRPC call failed",
    );

    // UNAVAILABLE → 502 Bad Gateway
    if (code === GRPC_STATUS.UNAVAILABLE) {
      return new BadGatewayError(
        `Downstream ${this.serviceName}.${rpc} unavailable`,
        { service: this.serviceName, rpc, grpcCode: code },
      );
    }

    // UNAUTHENTICATED → 映射为 BadGateway（BFF 自身做身份校验，下游不应返回 UNAUTHENTICATED）
    if (code === GRPC_STATUS.UNAUTHENTICATED) {
      return new BadGatewayError(
        `Downstream ${this.serviceName}.${rpc} returned UNAUTHENTICATED`,
        { service: this.serviceName, rpc, grpcCode: code },
      );
    }

    // 其他错误统一映射为 BadGateway
    return new BadGatewayError(
      `Downstream ${this.serviceName}.${rpc} failed: ${message}`,
      {
        service: this.serviceName,
        rpc,
        grpcCode: code,
        details: grpcErr.details,
      },
    );
  }

  /** 执行 gRPC 调用并统一错误处理 + 超时控制 */
  protected async callGrpc<T>(
    rpc: string,
    fn: () => Promise<T>,
    timeoutMs = 3000,
  ): Promise<T> {
    const start = Date.now();
    try {
      const result = await Promise.race([
        fn(),
        this.createTimeout(timeoutMs, rpc),
      ]);
      const duration = Date.now() - start;
      this.log.debug(
        { rpc, durationMs: duration },
        "Downstream gRPC call succeeded",
      );
      return result;
    } catch (err) {
      throw this.mapGrpcError(err, rpc);
    }
  }

  private createTimeout(ms: number, rpc: string): Promise<never> {
    return new Promise((_, reject) => {
      setTimeout(() => {
        reject(
          new BadGatewayError(
            `Downstream ${this.serviceName}.${rpc} timed out after ${ms}ms`,
            { service: this.serviceName, rpc, timeoutMs: ms },
          ),
        );
      }, ms);
    });
  }

  /** 降级模式 B：部分失败时返回 success=true + degraded=true + warning（president §2.6） */
  protected degraded<T>(
    data: T | null,
    warning: string,
    rpc: string,
  ): DownstreamResult<T> {
    this.log.warn({ rpc, warning }, "Downstream call degraded");
    return { success: true, data, degraded: true, warning };
  }

  /** 聚合失败（多下游部分失败且无降级数据，president §2.6） */
  protected aggregationFailed(
    errors: DownstreamError[],
  ): AggregationFailedError {
    return new AggregationFailedError(
      `Aggregation failed for ${this.serviceName}: ${errors.length} downstream(s) failed`,
      { service: this.serviceName, errors },
    );
  }
}
