import { Test } from '@nestjs/testing';
import { PlanEventReason, PlanEventType, ReleaseReason } from '@pac/types';
import { PlanAssignmentService } from '../src/modules/plan/plan-assignment.service';
import { PrismaService } from '../src/prisma/prisma.service';

/**
 * 批次跟踪的两条不变量 —— **都是 seed 数据当场抓出来的真 bug**,不是想出来的。
 *
 * 这两条错了都**不报任何错**,只是报表上的数字悄悄不对:
 *   ① agentStats 各人之和 ≠ planned  → 已退回/已到期的单从所有人名下蒸发
 *   ② 退回原因分布混进系统原因      → 退回率几乎翻倍
 * 主管看到的是一个看起来合理、实际错了的数,而"某人退了几条"恰恰是他最想看的列。
 */

const SCOPE = { hostId: 'h1', tenantId: 't1', sourceUnits: [] as string[], clinicIds: [] as string[], userId: 'u1' } as never;
const BATCH = 'b1';

function makePrisma(opts: {
  plans: Array<{ id: string; status: string; assigneeUserId: string | null; releaseReason: string | null }>;
  events: Array<{ planId: string; event: string; assigneeUserId: string | null; createdAt: Date }>;
}) {
  const eventFindMany = jest.fn(async (args: { where: Record<string, unknown>; orderBy?: unknown }) => {
    const w = args.where as { event?: string };
    let rows = opts.events.filter((e) => (w.event ? e.event === w.event : true));
    rows = [...rows].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); // desc
    return rows;
  });
  const prisma = {
    planAssignment: {
      findFirst: jest.fn(async () => ({
        id: BATCH, hostId: 'h1', tenantId: 't1', clinicId: 'c1', createdBy: 'leader-1',
        requestId: 'r1', criteria: {}, attributes: null,
        // ⭐ 批次头**晚于**事件 —— 正是当初那个时间窗过滤会踩的情形
        expiresAt: new Date('2026-08-05'), status: 'confirmed',
        revokedAt: null, revokedBy: null,
        createdAt: new Date('2026-08-10'), updatedAt: new Date('2026-08-10'),
      })),
    },
    followupPlan: {
      findMany: jest.fn(async () =>
        opts.plans.map((p) => ({ ...p, assignmentExpiresAt: null })),
      ),
    },
    planEventLog: { findMany: eventFindMany, groupBy: jest.fn(async () => []) },
    $queryRaw: jest.fn(async () => []),
  } as unknown as PrismaService;
  return { prisma, eventFindMany };
}

async function build(prisma: PrismaService): Promise<PlanAssignmentService> {
  const mod = await Test.createTestingModule({
    providers: [PlanAssignmentService, { provide: PrismaService, useValue: prisma }],
  })
    .useMocker(() => ({}))
    .compile();
  return mod.get(PlanAssignmentService);
}

describe('批次详情 —— agentStats 必须与 planned 对得上', () => {
  test('⭐⭐ 已退回的单(assignee 已清空)要按账本回捞原始承接人,不能蒸发', async () => {
    const { prisma } = makePrisma({
      plans: [
        { id: 'p1', status: 'assigned', assigneeUserId: 'a', releaseReason: null },
        { id: 'p2', status: 'assigned', assigneeUserId: 'a', releaseReason: null },
        // 已退回:assignee 为 null,只能靠 assign 事件找回是谁的
        { id: 'p3', status: 'active', assigneeUserId: null, releaseReason: ReleaseReason.OVER_CAPACITY },
      ],
      events: [
        { planId: 'p1', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
        { planId: 'p2', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
        { planId: 'p3', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
      ],
    });
    const svc = await build(prisma);
    const d = await svc.detail(SCOPE, BATCH);
    const sum = d.agentStats.reduce((acc, x) => acc + x.planned, 0);
    expect(sum).toBe(d.planned); // 3 = 3
    expect(d.agentStats.find((a) => a.userId === 'a')!.released).toBe(1);
  });

  test('⭐⭐ 事件时间**早于**批次头也要能回捞 —— 不许用时间窗过滤', async () => {
    // 这就是 seed 抓到的那一幕:事件写在 5 天前、批次头是刚建的,
    // 原实现 `createdAt >= head.createdAt` 把全部 assign 事件排掉,
    // 199 条里 50 条已退回/已到期的当场从 agentStats 消失,而**不报错**。
    const { prisma } = makePrisma({
      plans: [{ id: 'p1', status: 'active', assigneeUserId: null, releaseReason: 'over_capacity' }],
      events: [
        // 批次头是 2026-08-10,事件在 08-01
        { planId: 'p1', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
      ],
    });
    const svc = await build(prisma);
    const d = await svc.detail(SCOPE, BATCH);
    expect(d.agentStats.reduce((acc, x) => acc + x.planned, 0)).toBe(1);
    expect(d.agentStats[0]!.userId).toBe('a');
  });

  test('同一 plan 进过多个批次 → 取**最近一次** assign(当前 assignment_id 就是本批)', async () => {
    const { prisma } = makePrisma({
      plans: [{ id: 'p1', status: 'active', assigneeUserId: null, releaseReason: 'over_capacity' }],
      events: [
        { planId: 'p1', event: PlanEventType.ASSIGN, assigneeUserId: 'old', createdAt: new Date('2026-07-01') },
        { planId: 'p1', event: PlanEventType.ASSIGN, assigneeUserId: 'now', createdAt: new Date('2026-08-01') },
      ],
    });
    const svc = await build(prisma);
    const d = await svc.detail(SCOPE, BATCH);
    expect(d.agentStats[0]!.userId).toBe('now');
  });
});

describe('退回原因分布 —— 不得混进系统原因', () => {
  test('⭐⭐ 到期(auto_release)**不进**退回原因分布', async () => {
    // seed 实测:199 条里 22 条到期。若把它们算进退回,
    // 退回数从 28 变 50、退回率几乎翻倍,而报表看起来完全正常。
    const { prisma } = makePrisma({
      plans: [
        { id: 'p1', status: 'active', assigneeUserId: null, releaseReason: ReleaseReason.OVER_CAPACITY },
        // 到期回收:release_reason **为空**(那一列只属于客服的处置)
        { id: 'p2', status: 'active', assigneeUserId: null, releaseReason: null },
        { id: 'p3', status: 'active', assigneeUserId: null, releaseReason: null },
      ],
      events: [
        { planId: 'p1', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
        { planId: 'p2', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
        { planId: 'p3', event: PlanEventType.ASSIGN, assigneeUserId: 'a', createdAt: new Date('2026-08-01') },
      ],
    });
    const svc = await build(prisma);
    const d = await svc.detail(SCOPE, BATCH);
    expect(d.released).toBe(1); // 只有真退回那一条
    expect(d.releaseReasons).toHaveLength(1);
    expect(d.releaseReasons[0]!.reason).toBe(ReleaseReason.OVER_CAPACITY);
    // 到期的原因码绝不能出现在退回原因分布里
    expect(d.releaseReasons.map((r) => r.reason)).not.toContain(PlanEventReason.ASSIGNMENT_EXPIRED);
  });
});
