import { describe, it, expect, beforeEach, vi } from "vitest";
import type { IamClient } from "../../src/clients/iam.client.js";
import type { MsgClient } from "../../src/clients/msg.client.js";
import type { DataAnaClient } from "../../src/clients/data-ana.client.js";
import type { CoreEduClient } from "../../src/clients/core-edu.client.js";
import type {
  ChildDto,
  UserInfoDto,
  ViewportDto,
  NotificationDto,
  StudentWeaknessDto,
  LearningTrendDto,
  ClassPerformanceDto,
} from "../../src/clients/dtos.js";

// Mock safeRedis 以控制缓存行为（集成测试聚焦 resolver 编排逻辑）
vi.mock("../../src/shared/cache/redis.client.js", () => ({
  safeRedis: vi.fn().mockResolvedValue(null),
  getRedisClient: vi.fn(() => null),
  closeRedisClient: vi.fn(),
}));

import { buildDashboardResolver } from "../../src/graphql/resolvers/dashboard.resolver.js";
import { buildChildAnalyticsFieldResolver } from "../../src/graphql/resolvers/child.resolver.js";
import type { ResolverDeps } from "../../src/graphql/resolvers/index.js";
import type { GraphqlContext } from "../../src/graphql/context.js";
import type { ChildType } from "../../src/graphql/types.js";
import type { ChildGuard } from "../../src/aggregation/child-guard.js";
import { safeRedis } from "../../src/shared/cache/redis.client.js";

const mockSafeRedis = vi.mocked(safeRedis);

const DELAY_MS = 50;

function delay(ms: number): Promise<void> {
  return new Promise((r) => setTimeout(r, ms));
}

/**
 * 可追踪调用次数与延迟的 Mock IamClient
 */
class TrackingIamClient implements IamClient {
  readonly callLog: string[] = [];

  async getUserInfo(userId: string): Promise<UserInfoDto> {
    this.callLog.push(`getUserInfo:${userId}`);
    await delay(DELAY_MS);
    return {
      id: userId,
      email: "parent@example.com",
      name: "王家长",
      roles: ["parent"],
      permissions: [],
    };
  }

  async getChildrenByParent(_parentId: string): Promise<ChildDto[]> {
    this.callLog.push("getChildrenByParent");
    await delay(DELAY_MS);
    return [
      {
        id: "student-001",
        name: "李同学",
        grade: "三年级",
        classId: "class-001",
        className: "三年级1班",
        gradeId: "grade-003",
      },
      {
        id: "student-002",
        name: "李妹妹",
        grade: "一年级",
        classId: "class-002",
        className: "一年级2班",
        gradeId: "grade-001",
      },
      {
        id: "student-003",
        name: "王小宝",
        grade: "二年级",
        classId: "class-003",
        className: "二年级1班",
        gradeId: "grade-002",
      },
    ];
  }

  async getViewports(_userId: string): Promise<ViewportDto[]> {
    this.callLog.push("getViewports");
    await delay(DELAY_MS);
    return [
      {
        key: "dashboard",
        label: "首页",
        route: "/parent/dashboard",
        icon: "home",
        sortOrder: "1",
        requiredPermission: "parent:dashboard:view",
      },
    ];
  }

  async getEffectivePermissions(_userId: string): Promise<string[]> {
    return [];
  }
}

/**
 * 可追踪调用次数与延迟的 Mock MsgClient
 */
class TrackingMsgClient implements MsgClient {
  readonly callLog: string[] = [];

  async listNotifications(
    parentId: string,
    unreadOnly = false,
  ): Promise<NotificationDto[]> {
    this.callLog.push(`listNotifications:${parentId}:${unreadOnly}`);
    await delay(DELAY_MS);
    return [
      {
        id: "notif-1",
        userId: parentId,
        type: "GRADE",
        title: "成绩通知",
        content: "测试",
        channel: "APP",
        isRead: false,
        childId: "student-001",
        createdAt: Date.now(),
      },
      {
        id: "notif-2",
        userId: parentId,
        type: "HOMEWORK",
        title: "作业通知",
        content: "测试",
        channel: "APP",
        isRead: false,
        childId: "student-002",
        createdAt: Date.now(),
      },
    ];
  }

  async markAsRead(_notificationId: string): Promise<void> {}

  async getNotificationPreferences(_parentId: string) {
    return {
      parentId: _parentId,
      channels: ["APP"],
      eventTypes: {
        gradeReleased: true,
        homeworkGraded: true,
        examPublished: true,
        attendanceAlert: true,
        schoolAnnouncement: true,
      },
    };
  }

  async updateNotificationPreferences(
    parentId: string,
    prefs: NotificationPreferencesDto,
  ) {
    return { ...prefs, parentId };
  }
}

import type { NotificationPreferencesDto } from "../../src/clients/dtos.js";

/**
 * 可追踪调用次数与延迟的 Mock DataAnaClient（用于多子女学情并行编排测试）
 */
class TrackingDataAnaClient implements DataAnaClient {
  readonly callLog: string[] = [];

  async getStudentWeakness(
    studentId: string,
    _subjectId: string,
  ): Promise<StudentWeaknessDto> {
    this.callLog.push(`getStudentWeakness:${studentId}`);
    await delay(DELAY_MS);
    return {
      studentId,
      weakPoints: [
        {
          knowledgePointId: `kp-${studentId}-1`,
          title: "分数加减法",
          mastery: 0.45,
        },
      ],
    };
  }

  async getLearningTrend(
    studentId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<LearningTrendDto> {
    this.callLog.push(`getLearningTrend:${studentId}`);
    await delay(DELAY_MS);
    return { studentId, points: [{ date: Date.now(), score: 80 }] };
  }

  async getClassPerformance(
    classId: string,
    _subjectId: string,
    _startDate: number,
    _endDate: number,
  ): Promise<ClassPerformanceDto> {
    this.callLog.push(`getClassPerformance:${classId}`);
    await delay(DELAY_MS);
    return {
      classId,
      averageScore: 82.5,
      passRate: 0.9,
      scores: [
        { studentId: "student-001", score: 85, grade: "A" },
        { studentId: "student-002", score: 78, grade: "B" },
        { studentId: "student-003", score: 92, grade: "A" },
      ],
    };
  }
}

/**
 * Mock CoreEduClient（ResolverDeps 必需）
 */
class StubCoreEduClient implements CoreEduClient {
  async listGradesByStudent(_studentId: string) {
    return [];
  }
  async listHomeworkByClass(_classId: string) {
    return [];
  }
  async listExamsByClass(_classId: string) {
    return [];
  }
  async getClass(classId: string) {
    return { id: classId, name: "测试班级", gradeId: "grade-001" };
  }
}

function buildMockContext(parentId: string): GraphqlContext {
  return {
    session: {
      parentId,
      roles: ["parent"],
      dataScope: "CHILDREN",
      traceId: "trace-test-001",
    },
    req: {} as never,
    res: {} as never,
    loaders: {} as never,
  };
}

describe("测试用例 4：多子女仪表盘并行编排（Integration）", () => {
  let iamClient: TrackingIamClient;
  let msgClient: TrackingMsgClient;

  beforeEach(() => {
    vi.clearAllMocks();
    mockSafeRedis.mockResolvedValue(null);
    iamClient = new TrackingIamClient();
    msgClient = new TrackingMsgClient();
  });

  describe("Dashboard 4 路并行编排", () => {
    it("4 个下游调用并行执行，总耗时 < 单次耗时 + 100ms", async () => {
      // safeRedis: miss → fn 执行 → set ok
      mockSafeRedis
        .mockResolvedValueOnce(null) // get → miss
        .mockResolvedValueOnce(undefined); // set → ok

      const deps: ResolverDeps = {
        iamClient,
        coreEduClient: new StubCoreEduClient(),
        dataAnaClient: new TrackingDataAnaClient(),
        msgClient,
        childGuard: {} as unknown as ChildGuard,
      };

      const resolver = buildDashboardResolver(deps);
      // 使用唯一 parentId 避免 LRU 缓存污染
      const ctx = buildMockContext("parent-dash-parallel");

      const start = Date.now();
      const result = await resolver(null, {}, ctx);
      const elapsed = Date.now() - start;

      // 4 个并行调用：getUserInfo + getChildrenByParent + getViewports + listNotifications
      expect(iamClient.callLog).toContain("getUserInfo:parent-dash-parallel");
      expect(iamClient.callLog).toContain("getChildrenByParent");
      expect(iamClient.callLog).toContain("getViewports");
      expect(msgClient.callLog).toContain(
        "listNotifications:parent-dash-parallel:true",
      );

      // 并行：总耗时应接近 DELAY_MS，而非 4 * DELAY_MS
      // 允许 100ms 的 overhead
      expect(elapsed).toBeLessThan(DELAY_MS + 100);

      // 数据正确性
      expect(result.parent).not.toBeNull();
      expect(result.parent?.id).toBe("parent-dash-parallel");
      expect(result.children).toHaveLength(3);
      expect(result.viewports).toHaveLength(1);
      expect(result.unreadNotifications).toBe(2);
      expect(result.degraded).toBe(false);
    });

    it("返回 3 个子女数据", async () => {
      mockSafeRedis
        .mockResolvedValueOnce(null)
        .mockResolvedValueOnce(undefined);

      const deps: ResolverDeps = {
        iamClient,
        coreEduClient: new StubCoreEduClient(),
        dataAnaClient: new TrackingDataAnaClient(),
        msgClient,
        childGuard: {} as unknown as ChildGuard,
      };

      const resolver = buildDashboardResolver(deps);
      // 使用唯一 parentId 避免 LRU 缓存污染
      const result = await resolver(
        null,
        {},
        buildMockContext("parent-dash-children"),
      );

      expect(result.children).toHaveLength(3);
      expect(result.children[0]!.id).toBe("student-001");
      expect(result.children[1]!.id).toBe("student-002");
      expect(result.children[2]!.id).toBe("student-003");
    });
  });

  describe("3 子女 × 3 下游 = 9 路并行 gRPC（学情诊断）", () => {
    it("3 个子女的学情诊断并行编排，总调用 9 次，总耗时 < 单子女耗时 + 100ms", async () => {
      const dataAnaClient = new TrackingDataAnaClient();

      const deps: ResolverDeps = {
        iamClient,
        coreEduClient: new StubCoreEduClient(),
        dataAnaClient,
        msgClient,
        childGuard: {} as unknown as ChildGuard,
      };

      // 3 个子女的 ChildType（模拟 dashboard 返回的 children）
      const children: ChildType[] = [
        {
          id: "student-001",
          name: "李同学",
          grade: "三年级",
          class: { id: "class-001", name: "三年级1班", gradeId: "grade-003" },
        },
        {
          id: "student-002",
          name: "李妹妹",
          grade: "一年级",
          class: { id: "class-002", name: "一年级2班", gradeId: "grade-001" },
        },
        {
          id: "student-003",
          name: "王小宝",
          grade: "二年级",
          class: { id: "class-003", name: "二年级1班", gradeId: "grade-002" },
        },
      ];

      const analyticsResolver = buildChildAnalyticsFieldResolver(deps);
      const ctx = buildMockContext("parent-001");

      // 并行为 3 个子女获取学情诊断
      const start = Date.now();
      const results = await Promise.all(
        children.map((child) => analyticsResolver(child, {}, ctx)),
      );
      const elapsed = Date.now() - start;

      // 3 子女 × 3 下游 = 9 次调用
      expect(dataAnaClient.callLog).toHaveLength(9);

      // 每个子女调了 3 个 data-ana 方法
      const weaknessCalls = dataAnaClient.callLog.filter((c) =>
        c.startsWith("getStudentWeakness:"),
      );
      const trendCalls = dataAnaClient.callLog.filter((c) =>
        c.startsWith("getLearningTrend:"),
      );
      const classPerfCalls = dataAnaClient.callLog.filter((c) =>
        c.startsWith("getClassPerformance:"),
      );
      expect(weaknessCalls).toHaveLength(3);
      expect(trendCalls).toHaveLength(3);
      expect(classPerfCalls).toHaveLength(3);

      // 并行：总耗时接近 DELAY_MS，而非 3 * DELAY_MS
      expect(elapsed).toBeLessThan(DELAY_MS + 100);

      // 每个子女都有学情结果
      expect(results).toHaveLength(3);
      for (const analytics of results) {
        expect(analytics.weakness).toHaveLength(1);
        expect(analytics.trend).toHaveLength(1);
        expect(analytics.classRank).not.toBeNull();
      }
    });
  });

  describe("下游部分失败降级（测试用例 5 在 Integration 场景）", () => {
    it("msg.listNotifications 失败时 dashboard.degraded=true，其他数据正常返回", async () => {
      // 使用一个 msg 失败的 client
      const failingMsgClient: MsgClient = {
        listNotifications: async () => {
          await delay(DELAY_MS);
          throw new Error("msg service unavailable");
        },
        markAsRead: async () => {},
        getNotificationPreferences: async (parentId: string) => ({
          parentId,
          channels: ["APP"] as string[],
          eventTypes: {
            gradeReleased: true,
            homeworkGraded: true,
            examPublished: true,
            attendanceAlert: true,
            schoolAnnouncement: true,
          },
        }),
        updateNotificationPreferences: async (
          parentId: string,
          prefs: NotificationPreferencesDto,
        ) => ({
          ...prefs,
          parentId,
        }),
      };

      mockSafeRedis
        .mockResolvedValueOnce(null)
        .mockResolvedValueOnce(undefined);

      const deps: ResolverDeps = {
        iamClient,
        coreEduClient: new StubCoreEduClient(),
        dataAnaClient: new TrackingDataAnaClient(),
        msgClient: failingMsgClient,
        childGuard: {} as unknown as ChildGuard,
      };

      const resolver = buildDashboardResolver(deps);
      // 使用唯一 parentId 避免 LRU 缓存污染
      const result = await resolver(
        null,
        {},
        buildMockContext("parent-dash-fail"),
      );

      // msg 失败 → degraded=true
      expect(result.degraded).toBe(true);

      // 其他数据仍正常返回
      expect(result.parent).not.toBeNull();
      expect(result.children).toHaveLength(3);
      expect(result.viewports).toHaveLength(1);
      expect(result.unreadNotifications).toBe(0);
    });
  });
});
