/**
 * GraphQL Yoga 配置 - 加载 schema + context + error formatter.
 *
 * 仲裁依据:
 *   - coord-final-decisions §2 B1 (P2 起直接 GraphQL Yoga + DataLoader)
 *   - president-final-rulings §2.2 (GraphQL schema 存放 packages/shared-ts/contracts/graphql/)
 *   - coord-final-decisions §1 G8 (错误响应格式: GraphQL errors 数组 + extensions)
 *
 * Schema 文件: packages/shared-ts/contracts/graphql/student-bff.schema.graphql
 *
 * 集成方式: GraphQL Yoga 作为 Express middleware 挂载到 NestJS HTTP Adapter,
 *   路径 POST /graphql,开发环境启用 Playground.
 */
import { promises, existsSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createYoga, type YogaServerInstance } from "graphql-yoga";
import { makeExecutableSchema } from "@graphql-tools/schema";
import type { Request, Response } from "express";
import type { DownstreamClient } from "@edu/shared-ts/bff";
import { env } from "../../config/env.js";
import { logger } from "../observability/logger.js";
import type { Redis } from "ioredis";
import { CacheService } from "../cache/cache.module.js";
import {
  createDataLoaders,
  type StudentBffDataLoaders,
} from "../../student/dataloaders/data-loader.module.js";
import {
  extractUserIdFromRequest,
  extractTraceIdFromRequest,
  extractUserRolesFromRequest,
} from "../../student/guards/authorization.guard.js";

/**
 * makeExecutableSchema 接受的 resolver 类型 (从函数签名推断, 避免额外依赖).
 */
type SchemaResolvers = NonNullable<
  Parameters<typeof makeExecutableSchema>[0]["resolvers"]
>;

/**
 * GraphQL Context (每个请求一份).
 */
export interface StudentBffContext {
  userId: string | null;
  traceId: string;
  userRoles: string[];
  downstream: DownstreamClient;
  redis: Redis;
  cache: CacheService;
  dataLoaders: StudentBffDataLoaders;
  requestId: string;
}

/**
 * GraphQL schema 文件路径.
 * 多路径探测：兼容从项目根目录或服务目录运行（pnpm --filter cwd 为服务目录）.
 */
const _schemaDir = path.dirname(fileURLToPath(import.meta.url));
const SCHEMA_CANDIDATES = [
  path.resolve(
    process.cwd(),
    "packages/shared-ts/contracts/graphql/student-bff.schema.graphql",
  ),
  path.resolve(
    process.cwd(),
    "../../packages/shared-ts/contracts/graphql/student-bff.schema.graphql",
  ),
  path.resolve(
    _schemaDir,
    "../../../../../packages/shared-ts/contracts/graphql/student-bff.schema.graphql",
  ),
];
const SCHEMA_PATH: string =
  SCHEMA_CANDIDATES.find((p) => existsSync(p)) ?? SCHEMA_CANDIDATES[0]!;

/**
 * 加载 schema SDL 文本.
 */
export async function loadSchemaSDL(): Promise<string> {
  try {
    return await promises.readFile(SCHEMA_PATH, "utf-8");
  } catch (err) {
    logger.error(
      { err, path: SCHEMA_PATH },
      "Failed to load student-bff GraphQL schema file",
    );
    throw err;
  }
}

/**
 * 创建 GraphQL Yoga 实例.
 *
 * @param resolvers GraphQL Resolver 映射表 (由 StudentModule 装配)
 * @param downstream DownstreamClient 实例 (由 NestJS DI 注入)
 * @param redis Redis 客户端 (由 NestJS DI 注入)
 * @param cache CacheService 实例 (由 NestJS DI 注入)
 */
export async function createStudentBffYoga(
  resolvers: SchemaResolvers,
  downstream: DownstreamClient,
  redis: Redis,
  cache: CacheService,
): Promise<
  YogaServerInstance<{ req: Request; res: Response }, StudentBffContext>
> {
  const typeDefs = await loadSchemaSDL();

  const schema = makeExecutableSchema({
    typeDefs,
    resolvers,
  });

  const yoga = createYoga<
    {
      req: Request;
      res: Response;
    },
    StudentBffContext
  >({
    schema,
    graphqlEndpoint: "/graphql",
    // graphql-yoga v5：request 是 WHATWG Request，headers 需用 .get() 提取
    context: ({ request }): StudentBffContext => {
      const req = {
        headers: {
          "x-user-id": request.headers.get("x-user-id") ?? undefined,
          "x-request-id": request.headers.get("x-request-id") ?? undefined,
          "x-user-roles": request.headers.get("x-user-roles") ?? undefined,
        },
      } as unknown as Request;
      const userId = extractUserIdFromRequest(req);
      const traceId = extractTraceIdFromRequest(req);
      const userRoles = extractUserRolesFromRequest(req);
      return {
        userId,
        traceId,
        userRoles,
        downstream,
        redis,
        cache,
        dataLoaders: createDataLoaders(downstream),
        requestId: traceId,
      };
    },
    logging: {
      debug: (msg) => logger.debug({ component: "graphql-yoga" }, String(msg)),
      info: (msg) => logger.info({ component: "graphql-yoga" }, String(msg)),
      warn: (msg) => logger.warn({ component: "graphql-yoga" }, String(msg)),
      error: (msg) => logger.error({ component: "graphql-yoga" }, String(msg)),
    },
    maskedErrors: env.NODE_ENV === "production",
    // 开发环境启用 Playground
    graphiql: env.GRAPHQL_PLAYGROUND && env.NODE_ENV === "development",
  });

  logger.info(
    { endpoint: "/graphql", playground: env.GRAPHQL_PLAYGROUND },
    "GraphQL Yoga initialized",
  );

  return yoga;
}
