import { classifyPlanProgress, type PlanProgress } from '../src/modules/plan/plan-assignment.service';

/**
 * 「处理了没」的判据回归(产品裁决 2026-08-02)。
 *
 * 口径:**看池子状态,不等回写** ——
 *   这个患者的召回还在不在池(出池 = 引擎判定无活信号)、有没有被抑制(客服写了回访结果),
 *   两者任一成立就算处理过了。⚠️ 是**处理**不是**成功**。
 *
 * 🔴 这里最贵的一条是「五桶穷尽」:少一个分支,done + pending 就加不出 planned,
 * 主管一对数发现少了人,而少的那几个**永远查不出去哪了**。
 */

const NOW = new Date('2026-08-02T12:00:00.000Z');
const future = new Date('2026-09-01T00:00:00.000Z');
const past = new Date('2026-07-01T00:00:00.000Z');

const p = (over: Partial<Parameters<typeof classifyPlanProgress>[0]>) =>
  classifyPlanProgress(
    { status: 'active', assigneeUserId: null, snoozedUntil: null, ...over },
    NOW,
  );

describe('处理判据 —— 出池(零回写依赖的那一半)', () => {
  test('⭐⭐ superseded = 引擎判定该患者已无活信号 → **已处理**', () => {
    // 这是整套口径的基石:治疗真做了 / 诊断没了 → closeStaleActivePlan 把它 supersede。
    // 客服写不写回访结果都成立 —— 生产回写率只有 11%,靠回写等于 89% 的活白干。
    expect(p({ status: 'superseded' })).toBe('resolved');
  });

  test('⭐ superseded 优先于一切 —— 就算还挂在客服名下也算出池', () => {
    expect(p({ status: 'superseded', assigneeUserId: 'a1' })).toBe('resolved');
  });

  test('completed / abandoned → 已结案', () => {
    expect(p({ status: 'completed' })).toBe('closed');
    expect(p({ status: 'abandoned' })).toBe('closed');
  });
});

describe('处理判据 —— 抑制(有回写的那一半)', () => {
  test('⭐⭐ snoozedUntil 在未来 = 客服写了回访结果 → **已处理**', () => {
    expect(p({ status: 'assigned', assigneeUserId: 'a1', snoozedUntil: future })).toBe('suppressed');
  });

  test('⭐⭐ 抑制**优先于在手** —— 约了下次回访,单子还在他名下,但这一单确实处理过了', () => {
    // 排成 inHandPending 会让「已处理」少算一大块,而那正是唯一有回写的那部分。
    expect(p({ status: 'assigned', assigneeUserId: 'a1', snoozedUntil: future })).not.toBe('inHandPending');
  });

  test('⭐ 抑制**过期了**就不算处理了(到点该重新浮现)', () => {
    expect(p({ status: 'assigned', assigneeUserId: 'a1', snoozedUntil: past })).toBe('inHandPending');
    expect(p({ status: 'active', assigneeUserId: null, snoozedUntil: past })).toBe('backToPool');
  });
});

describe('处理判据 —— 未处理的两种', () => {
  test('还在客服手上、没写任何结果 → 在手未处理', () => {
    expect(p({ status: 'assigned', assigneeUserId: 'a1' })).toBe('inHandPending');
  });

  test('⭐ 退回 / 到期 / 撤销放回 → 回池(= 没处理成)', () => {
    expect(p({ status: 'active', assigneeUserId: null })).toBe('backToPool');
  });
});

describe('⭐⭐ 五桶必须穷尽 —— 加不出 planned 就是静默丢人', () => {
  test('任何 (status × assignee × snoozed) 组合都落进恰好一个桶', () => {
    const statuses = ['active', 'assigned', 'completed', 'abandoned', 'superseded', '未来新增的状态'];
    const assignees = [null, 'a1'];
    const snoozes = [null, future, past];
    const buckets: PlanProgress[] = ['resolved', 'suppressed', 'closed', 'inHandPending', 'backToPool'];
    for (const status of statuses) {
      for (const assigneeUserId of assignees) {
        for (const snoozedUntil of snoozes) {
          const got = classifyPlanProgress({ status, assigneeUserId, snoozedUntil }, NOW);
          // ⛔ 兜底必须存在:哪天有人加了个新 status,不能让它静默消失
          expect(buckets).toContain(got);
        }
      }
    }
  });

  test('⭐ done + inHandPending + backToPool 恒等于总数', () => {
    const rows = [
      { status: 'superseded', assigneeUserId: null, snoozedUntil: null },
      { status: 'superseded', assigneeUserId: 'a1', snoozedUntil: null },
      { status: 'assigned', assigneeUserId: 'a1', snoozedUntil: future },
      { status: 'completed', assigneeUserId: 'a1', snoozedUntil: null },
      { status: 'assigned', assigneeUserId: 'a2', snoozedUntil: null },
      { status: 'active', assigneeUserId: null, snoozedUntil: null },
      { status: 'active', assigneeUserId: null, snoozedUntil: past },
    ];
    const c = { resolved: 0, suppressed: 0, closed: 0, inHandPending: 0, backToPool: 0 };
    for (const r of rows) c[classifyPlanProgress(r, NOW)]++;
    const done = c.resolved + c.suppressed + c.closed;
    expect(done + c.inHandPending + c.backToPool).toBe(rows.length);
    expect(done).toBe(4); // 2 出池 + 1 抑制 + 1 结案
    expect(c.inHandPending).toBe(1);
    expect(c.backToPool).toBe(2);
  });
});
