import type { GraphqlContext } from "../context.js";
import type {
  ChildAnalyticsType,
  ChildType,
  ExamType,
  GradePageType,
  GradeType,
  HomeworkType,
} from "../types.js";
import type { ResolverDeps } from "./index.js";
import {
  mapAnalytics,
  mapChild,
  mapExam,
  mapGrade,
  mapHomework,
} from "../../aggregation/response-mapper.js";
import { orchestrate } from "../../aggregation/orchestrator.js";
import type {
  ClassPerformanceDto,
  LearningTrendDto,
  StudentWeaknessDto,
} from "../../clients/dtos.js";

/**
 * Child Resolvers（对齐 02-architecture-design.md §1.3 + workline P4.5）。
 *
 * 包含：
 * - Query.children：返回家长绑定的孩子列表
 * - Query.child：单个孩子查询（含 ChildGuard 校验）
 * - Query.childHomework：孩子作业列表（含 ChildGuard 校验）
 * - Query.childExams：孩子考试列表（含 ChildGuard 校验）
 * - Query.childAnalytics：孩子学情诊断（含 ChildGuard 校验）
 * - Child.lastGrade：字段解析器，最近一次成绩
 * - Child.grades：字段解析器，成绩分页
 * - Child.homework：字段解析器，作业列表
 * - Child.exams：字段解析器，考试列表
 * - Child.analytics：字段解析器，学情诊断
 *
 * ChildGuard 校验（02 §2.3）：
 * - child-scoped query 需先调 ChildGuard.validateChildAccess
 * - 越权 childId 抛 BFF_PARENT_CHILD_NOT_BOUND(403)
 *
 * 字段解析器不需要 ChildGuard 校验（parent 已通过合法路径获取 Child 对象）。
 * 字段解析器通过 ctx.loaders 访问 per-request DataLoader 防止 N+1。
 */

// ============ Query Resolvers ============

export function buildChildrenQueryResolver(deps: ResolverDeps) {
  return async (
    _parent: unknown,
    _args: unknown,
    ctx: GraphqlContext,
  ): Promise<ChildType[]> => {
    const children = await deps.childGuard.getBoundChildren(
      ctx.session.parentId,
    );
    return children.map(mapChild);
  };
}

export function buildChildQueryResolver(deps: ResolverDeps) {
  return async (
    _parent: unknown,
    args: { childId: string },
    ctx: GraphqlContext,
  ): Promise<ChildType | null> => {
    await deps.childGuard.validateChildAccess(
      ctx.session.parentId,
      args.childId,
    );

    const children = await deps.childGuard.getBoundChildren(
      ctx.session.parentId,
    );
    const child = children.find((c) => c.id === args.childId);
    return child ? mapChild(child) : null;
  };
}

export function buildChildHomeworkQueryResolver(deps: ResolverDeps) {
  return async (
    _parent: unknown,
    args: { childId: string },
    ctx: GraphqlContext,
  ): Promise<HomeworkType[]> => {
    await deps.childGuard.validateChildAccess(
      ctx.session.parentId,
      args.childId,
    );

    const children = await deps.childGuard.getBoundChildren(
      ctx.session.parentId,
    );
    const child = children.find((c) => c.id === args.childId);
    if (!child) return [];

    const homework = await ctx.loaders.homework.load(child.classId);
    return homework.map(mapHomework);
  };
}

export function buildChildExamsQueryResolver(deps: ResolverDeps) {
  return async (
    _parent: unknown,
    args: { childId: string },
    ctx: GraphqlContext,
  ): Promise<ExamType[]> => {
    await deps.childGuard.validateChildAccess(
      ctx.session.parentId,
      args.childId,
    );

    const children = await deps.childGuard.getBoundChildren(
      ctx.session.parentId,
    );
    const child = children.find((c) => c.id === args.childId);
    if (!child) return [];

    const exams = await ctx.loaders.exams.load(child.classId);
    return exams.map(mapExam);
  };
}

export function buildChildAnalyticsQueryResolver(deps: ResolverDeps) {
  return async (
    _parent: unknown,
    args: { childId: string; dateRange?: { start: string; end: string } },
    ctx: GraphqlContext,
  ): Promise<ChildAnalyticsType> => {
    await deps.childGuard.validateChildAccess(
      ctx.session.parentId,
      args.childId,
    );

    const children = await deps.childGuard.getBoundChildren(
      ctx.session.parentId,
    );
    const child = children.find((c) => c.id === args.childId);
    if (!child) {
      return {
        childId: args.childId,
        weakness: [],
        trend: [],
        classRank: null,
        classAverage: null,
      };
    }

    return loadChildAnalytics(
      deps,
      args.childId,
      child.classId,
      args.dateRange,
    );
  };
}

// ============ Child Field Resolvers ============

export function buildChildLastGradeFieldResolver() {
  return async (
    parent: ChildType,
    _args: unknown,
    ctx: GraphqlContext,
  ): Promise<GradeType | null> => {
    const grades = await ctx.loaders.grades.load(parent.id);
    if (grades.length === 0) return null;

    const sorted = [...grades].sort(
      (a, b) =>
        new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
    );
    const latest = sorted[0];
    if (!latest) return null;
    return mapGrade(latest);
  };
}

export function buildChildGradesFieldResolver() {
  return async (
    parent: ChildType,
    args: { page?: number; pageSize?: number; subject?: string },
    ctx: GraphqlContext,
  ): Promise<GradePageType> => {
    const grades = await ctx.loaders.grades.load(parent.id);
    const mapped = grades.map(mapGrade);

    const filtered = args.subject
      ? mapped.filter((g) => g.subject === args.subject)
      : mapped;

    const page = args.page ?? 1;
    const pageSize = args.pageSize ?? 20;
    const start = (page - 1) * pageSize;
    const items = filtered.slice(start, start + pageSize);

    return {
      items,
      pagination: { page, pageSize, total: filtered.length },
    };
  };
}

export function buildChildHomeworkFieldResolver() {
  return async (
    parent: ChildType,
    _args: unknown,
    ctx: GraphqlContext,
  ): Promise<HomeworkType[]> => {
    const homework = await ctx.loaders.homework.load(parent.class.id);
    return homework.map(mapHomework);
  };
}

export function buildChildExamsFieldResolver() {
  return async (
    parent: ChildType,
    _args: unknown,
    ctx: GraphqlContext,
  ): Promise<ExamType[]> => {
    const exams = await ctx.loaders.exams.load(parent.class.id);
    return exams.map(mapExam);
  };
}

export function buildChildAnalyticsFieldResolver(deps: ResolverDeps) {
  return async (
    parent: ChildType,
    args: { dateRange?: { start: string; end: string } },
    _ctx: GraphqlContext,
  ): Promise<ChildAnalyticsType> => {
    return loadChildAnalytics(deps, parent.id, parent.class.id, args.dateRange);
  };
}

// ============ Shared Helper ============

/**
 * 加载孩子学情诊断数据。
 *
 * 并行调用 3 个 data-ana RPC：
 * - getStudentWeakness
 * - getLearningTrend
 * - getClassPerformance
 *
 * 使用 Promise.allSettled 部分失败容忍。
 */
async function loadChildAnalytics(
  deps: ResolverDeps,
  childId: string,
  classId: string,
  dateRange?: { start: string; end: string },
): Promise<ChildAnalyticsType> {
  const now = Date.now();
  const startDate = dateRange
    ? new Date(dateRange.start).getTime()
    : now - 30 * 86400000;
  const endDate = dateRange ? new Date(dateRange.end).getTime() : now;

  const orchResult = await orchestrate({
    weakness: deps.dataAnaClient.getStudentWeakness(childId, ""),
    trend: deps.dataAnaClient.getLearningTrend(childId, startDate, endDate),
    classPerf: deps.dataAnaClient.getClassPerformance(
      classId,
      "",
      startDate,
      endDate,
    ),
  });

  const weakness = (orchResult.results
    .weakness as StudentWeaknessDto | null) ?? {
    studentId: childId,
    weakPoints: [],
  };
  const trend = (orchResult.results.trend as LearningTrendDto | null) ?? {
    studentId: childId,
    points: [],
  };
  const classPerf = orchResult.results.classPerf as ClassPerformanceDto | null;

  return mapAnalytics(childId, weakness, trend, classPerf);
}
