// gRPC Channel + Client 工厂（B2 裁决：首次实现即 gRPC）
// 使用 @grpc/grpc-js + @grpc/proto-loader 动态加载 proto（无生成代码依赖）
// 上游就绪后可切换为 @bufbuild/protobuf 生成代码（B8 裁决）
import * as grpc from "@grpc/grpc-js";
import * as protoLoader from "@grpc/proto-loader";
import path from "node:path";
import { env } from "../../config/env.js";
import { logger } from "../../shared/observability/logger.js";
import type { DownstreamServiceName } from "../types.js";

/** proto 文件根目录（monorepo 相对路径） */
const PROTO_ROOT = path.resolve(
  process.cwd(),
  "../../packages/shared-proto/proto",
);

/** 已加载的 proto 定义缓存（按文件名缓存） */
const packageCache = new Map<string, protoLoader.PackageDefinition>();

/** 已创建的 gRPC Client 缓存（按 service+target 缓存） */
const clientCache = new Map<string, grpc.Client>();

/** 各下游服务对应的 proto 文件 + 包名 + service 名 */
interface ServiceProtoConfig {
  protoFile: string;
  packageName: string;
  serviceName: string;
}

const SERVICE_PROTO_MAP: Record<DownstreamServiceName, ServiceProtoConfig> = {
  iam: {
    protoFile: "iam.proto",
    packageName: "next_edu_cloud.iam.v1",
    serviceName: "IamService",
  },
  "core-edu": {
    protoFile: "core_edu.proto",
    packageName: "next_edu_cloud.core_edu.v1",
    serviceName: "", // core-edu 多 service，按需指定
  },
  content: {
    protoFile: "content.proto",
    packageName: "next_edu_cloud.content.v1",
    serviceName: "",
  },
  "data-ana": {
    protoFile: "analytics.proto",
    packageName: "next_edu_cloud.analytics.v1",
    serviceName: "",
  },
  msg: {
    protoFile: "msg.proto",
    packageName: "next_edu_cloud.msg.v1",
    serviceName: "",
  },
  ai: {
    protoFile: "ai.proto",
    packageName: "next_edu_cloud.ai.v1",
    serviceName: "",
  },
};

/** 加载 proto 定义（带缓存） */
function loadPackageDefinition(
  protoFile: string,
): protoLoader.PackageDefinition {
  const cached = packageCache.get(protoFile);
  if (cached) return cached;

  const fullPath = path.join(PROTO_ROOT, protoFile);
  const def = protoLoader.loadSync(fullPath, {
    keepCase: false,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true,
    includeDirs: [PROTO_ROOT],
  });
  packageCache.set(protoFile, def);
  return def;
}

/** 获取下游服务的 gRPC target（从环境变量） */
export function getGrpcTarget(service: DownstreamServiceName): string {
  const map: Record<DownstreamServiceName, string> = {
    iam: env.IAM_GRPC_TARGET,
    "core-edu": env.CORE_EDU_GRPC_TARGET,
    content: env.CONTENT_GRPC_TARGET,
    "data-ana": env.DATA_ANA_GRPC_TARGET,
    msg: env.MSG_GRPC_TARGET,
    ai: env.AI_GRPC_TARGET,
  };
  return map[service];
}

/** 判断下游服务是否已配置 target（未配置则走 mock） */
export function isServiceConfigured(service: DownstreamServiceName): boolean {
  const target = getGrpcTarget(service);
  return target.length > 0;
}

/** 创建或获取 gRPC Client（带缓存） */
export function getGrpcClient(
  service: DownstreamServiceName,
  serviceName?: string,
): grpc.Client {
  const target = getGrpcTarget(service);
  const config = SERVICE_PROTO_MAP[service];
  const svcName = serviceName ?? config.serviceName;

  if (!target) {
    throw new Error(
      `gRPC target not configured for service: ${service} (set ${service.toUpperCase().replace("-", "_")}_GRPC_TARGET)`,
    );
  }

  if (!svcName) {
    throw new Error(
      `Service name not specified for ${service}, pass serviceName explicitly`,
    );
  }

  const cacheKey = `${service}:${svcName}:${target}`;
  const cached = clientCache.get(cacheKey);
  if (cached) return cached;

  const def = loadPackageDefinition(config.protoFile);
  const proto = grpc.loadPackageDefinition(def) as unknown as Record<
    string,
    Record<string, unknown>
  >;

  // 按包路径查找 Service 构造器
  const pkgParts = config.packageName.split(".");
  let pkg: Record<string, unknown> = proto;
  for (const part of pkgParts) {
    pkg = pkg[part] as Record<string, unknown>;
    if (!pkg) {
      throw new Error(
        `Package ${config.packageName} not found in ${config.protoFile}`,
      );
    }
  }

  const ServiceConstructor = pkg[svcName] as unknown as {
    new (target: string, credentials: grpc.ChannelCredentials): grpc.Client;
  };
  if (!ServiceConstructor) {
    throw new Error(
      `Service ${svcName} not found in package ${config.packageName}`,
    );
  }

  // P2-P2 阶段使用 insecure（内网，mTLS 在 P6 硬化阶段启用）
  const client = new ServiceConstructor(
    target,
    grpc.credentials.createInsecure(),
  );

  clientCache.set(cacheKey, client);
  logger.info({ service, serviceName: svcName, target }, "gRPC client created");
  return client;
}

/** 创建 gRPC metadata（注入 x-user-id + x-request-id） */
export function createGrpcMetadata(
  userId: string,
  traceId?: string,
): grpc.Metadata {
  const meta = new grpc.Metadata();
  meta.add("x-user-id", userId);
  if (traceId) {
    meta.add("x-request-id", traceId);
  }
  return meta;
}

/** 关闭所有 gRPC Client（优雅关闭时调用） */
export function closeAllGrpcClients(): void {
  for (const [key, client] of clientCache) {
    try {
      client.close();
      logger.info({ client: key }, "gRPC client closed");
    } catch (err) {
      logger.warn({ client: key, err }, "Failed to close gRPC client");
    }
  }
  clientCache.clear();
}

/**
 * 执行 gRPC 健康检查（grpc.health.v1.Health/Check）
 * 用于 /readyz 探针（president §2.4）
 *
 * 使用标准 grpc.health.v1.Health 服务，通过 makeUnaryRequest 直接调用。
 * 超时 2s，返回 SERVING / NOT_SERVING / UNREACHABLE。
 */
export async function checkGrpcHealth(
  service: DownstreamServiceName,
): Promise<{ serving: boolean; latencyMs: number; error?: string }> {
  const target = getGrpcTarget(service);
  if (!target) {
    return { serving: false, latencyMs: 0, error: "target not configured" };
  }

  const start = Date.now();
  const client = new grpc.Client(target, grpc.credentials.createInsecure(), {
    "grpc.enable_retries": 0,
  });

  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      client.close();
      resolve({
        serving: false,
        latencyMs: Date.now() - start,
        error: "health check timed out (2s)",
      });
    }, 2000);

    client.makeUnaryRequest(
      "grpc.health.v1.Health/Check",
      // serialize: HealthCheckRequest → Buffer（空消息体）
      () => Buffer.alloc(0),
      // deserialize: Buffer → { status: string }
      (buf: Buffer) => {
        // proto HealthCheckResponse { enum ServingStatus { SERVING = 1; } int32 status = 1; }
        // 简化解析：直接读取第一个 varint 字段（field 1 = status）
        if (buf.length < 2) return { status: "UNKNOWN" };
        const statusByte: number = buf[1] ?? 0;
        const statusMap: Record<number, string> = {
          0: "UNKNOWN",
          1: "SERVING",
          2: "NOT_SERVING",
          3: "SERVICE_UNKNOWN",
        };
        return { status: statusMap[statusByte] ?? "UNKNOWN" };
      },
      {},
      new grpc.Metadata(),
      {},
      (err: grpc.ServiceError | null, value: unknown) => {
        clearTimeout(timeout);
        const latencyMs = Date.now() - start;
        client.close();
        if (err) {
          resolve({ serving: false, latencyMs, error: err.message });
        } else {
          const status = (value as { status?: string } | null | undefined)
            ?.status;
          resolve({ serving: status === "SERVING", latencyMs });
        }
      },
    );
  });
}
