import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import {
  credentials,
  loadPackageDefinition,
  type ChannelCredentials,
} from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import { logger } from "../../shared/observability/logger.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

const PROTO_ROOT = resolve(
  __dirname,
  "../../../../packages/shared-proto/proto",
);

const LOADER_OPTIONS: protoLoader.Options = {
  keepCase: false,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

/**
 * gRPC client 工厂（对齐 02-architecture-design.md §1.4 clients/grpc/）。
 *
 * 使用 @grpc/proto-loader 动态加载 proto 文件，
 * 通过 @grpc/grpc-js 创建 Channel + Client 实例。
 *
 * proto-loader keepCase: false → snake_case → camelCase
 * longs: String → int64 → string
 * enums: String → enum → string
 *
 * 返回的 client 为动态类型（proto-loader 不生成静态 TS 类型），
 * 调用方通过 `as unknown as SpecificClient` 断言为具体接口。
 */
export function createGrpcClient<TService>(options: {
  protoFile: string;
  packageName: string;
  serviceName: string;
  target: string;
}): TService {
  const protoPath = resolve(PROTO_ROOT, options.protoFile);

  logger.info(
    {
      protoFile: options.protoFile,
      packageName: options.packageName,
      serviceName: options.serviceName,
      target: options.target,
    },
    "Creating gRPC client",
  );

  const packageDefinition = protoLoader.loadSync(protoPath, LOADER_OPTIONS);
  const protoDescriptor = loadPackageDefinition(packageDefinition);

  // Navigate: protoDescriptor.next_edu_cloud.iam.v1.IamService
  const packages = options.packageName.split(".");
  let current: unknown = protoDescriptor;
  for (const pkg of packages) {
    current = (current as Record<string, unknown>)[pkg];
    if (current === undefined) {
      throw new Error(
        `gRPC package "${options.packageName}" not found in proto ${options.protoFile}`,
      );
    }
  }

  const ServiceConstructor = (current as Record<string, unknown>)[
    options.serviceName
  ] as {
    new (target: string, creds: ChannelCredentials): TService;
  };

  if (!ServiceConstructor) {
    throw new Error(
      `gRPC service "${options.serviceName}" not found in package "${options.packageName}"`,
    );
  }

  // proto-loader 动态加载的 client 构造函数类型未知，
  // 从 unknown 断言为目标接口（项目规则允许从 unknown 转换）
  return new ServiceConstructor(
    options.target,
    credentials.createInsecure(),
  ) as unknown as TService;
}

/**
 * 将 gRPC callback 风格调用转为 Promise，同时记录 metrics。
 *
 * proto-loader 加载的 client 方法签名为：
 *   client.Method(request, metadata?, callback)
 *
 * 此辅助函数省略 metadata 参数，仅处理 request → callback → Promise。
 * 需要 metadata 的调用（如传递 trace context）应直接操作 raw client。
 */
export function callGrpc<TReq, TRes>(
  client: unknown,
  method: string,
  request: TReq,
  serviceName: string,
  timeoutMs = 5000,
): Promise<TRes> {
  const rawClient = client as Record<string, unknown>;
  const fn = rawClient[method] as (
    req: TReq,
    callback: (err: Error | null, res: TRes) => void,
  ) => void;

  if (typeof fn !== "function") {
    return Promise.reject(
      new Error(`gRPC method "${method}" not found on ${serviceName} client`),
    );
  }

  return new Promise<TRes>((resolvePromise, reject) => {
    const timer = setTimeout(() => {
      reject(
        new Error(
          `gRPC call ${serviceName}.${method} timed out after ${timeoutMs}ms`,
        ),
      );
    }, timeoutMs);

    fn.call(rawClient, request, (err, res) => {
      clearTimeout(timer);
      if (err) {
        reject(err);
      } else {
        resolvePromise(res);
      }
    });
  });
}
