import { AssignStrategy } from '@pac/types';
import { PlanAssignmentService } from '../src/modules/plan/plan-assignment.service';
import type { PrismaService } from '../src/prisma/prisma.service';
import type { AgentRosterService } from '../src/modules/plan/agent-roster.service';
import type { TenantScopeContext } from '../src/common/decorators/tenant-scope.decorator';

/**
 * 「为什么这个人给了张悦」——**从分配当时的决策快照回答**。
 *
 * 🔴 这个工具存在的理由：在此之前模型被问到只能**推理**，而推理出来的理由
 * 听起来完全合理，主管照着去调策略就是白跑一趟。
 * ⇒ 所以这里锁的核心不变式是：**查不到就如实说查不到，⛔ 不给一个能顺着编的空壳。**
 */

const SCOPE: TenantScopeContext = {
  hostId: 'h',
  tenantId: 't',
  clinicIds: ['c1'],
  sourceUnits: [],
  userId: 'u',
};

function svc(plan: unknown) {
  const prisma = {
    followupPlan: { findFirst: jest.fn(async () => plan) },
  } as unknown as PrismaService;
  return new PlanAssignmentService(prisma, {} as AgentRosterService);
}

describe('explainAssignment', () => {
  test('🔴 查不到就如实说，⛔ 不返回空壳让模型顺着编', async () => {
    const r = await svc(null).explainAssignment(SCOPE, 'p1');
    expect(String(r['结论'])).toContain('没有已落库的分配记录');
    // ⛔ 一个能被当成"理由"的字段都不许有
    expect(r['怎么落到他头上的']).toBeUndefined();
    expect(r['分给了谁']).toBeUndefined();
  });

  test('⭐ 专属命中 → 说清是他本来就是专属客服', async () => {
    const r = await svc({
      assigneeUserId: 'a1',
      assignStrategy: AssignStrategy.DEDICATED,
      dedicatedCsAtAssign: 'a1',
      dedicatedCsLastVisitAt: new Date('2026-07-01T00:00:00Z'),
      priorityScoreAtAssign: 82,
      selectionMode: 'rank',
      assignmentId: 'b1',
      status: 'assigned',
    }).explainAssignment(SCOPE, 'p1');
    expect(r['分给了谁']).toBe('a1');
    expect(String(r['怎么落到他头上的'])).toContain('本来就是');
    expect(r['属于哪一批']).toBe('b1');
  });

  test('🔴 专属排满溢出 与 从来没有专属 —— 两种说法必须分得开', async () => {
    const overflow = await svc({
      assigneeUserId: 'a2',
      assignStrategy: AssignStrategy.SPREAD_OVERFLOW,
      dedicatedCsAtAssign: 'a1',
      dedicatedCsLastVisitAt: null,
      priorityScoreAtAssign: null,
      selectionMode: 'rank',
      assignmentId: 'b1',
      status: 'assigned',
    }).explainAssignment(SCOPE, 'p1');
    const none = await svc({
      assigneeUserId: 'a2',
      assignStrategy: AssignStrategy.SPREAD_NO_DEDICATED,
      dedicatedCsAtAssign: null,
      dedicatedCsLastVisitAt: null,
      priorityScoreAtAssign: null,
      selectionMode: 'rank',
      assignmentId: 'b1',
      status: 'assigned',
    }).explainAssignment(SCOPE, 'p1');
    // ⚠️ 主管据此做的事完全不同：前者要不要挪回专属；后者是这人根本没人管
    expect(String(overflow['怎么落到他头上的'])).toContain('已经排满');
    expect(String(none['怎么落到他头上的'])).toContain('没有可用的专属客服');
    expect(overflow['分配当时的专属客服']).toBe('a1');
    expect(String(none['分配当时的专属客服'])).toContain('当时没有');
  });

  test('⭐ 探索配额选进来的要说明白 —— 它**不是**按分选的', async () => {
    const r = await svc({
      assigneeUserId: 'a1',
      assignStrategy: AssignStrategy.MANUAL,
      dedicatedCsAtAssign: null,
      dedicatedCsLastVisitAt: null,
      priorityScoreAtAssign: 12,
      selectionMode: 'explore',
      assignmentId: 'b1',
      status: 'assigned',
    }).explainAssignment(SCOPE, 'p1');
    expect(String(r['怎么被选进这批的'])).toContain('不是按分选的');
    expect(String(r['怎么落到他头上的'])).toContain('手工指定');
  });

  test('🔴 快照缺列时说「没有记录」，⛔ 不许拿默认值冒充事实', async () => {
    const r = await svc({
      assigneeUserId: 'a1',
      assignStrategy: null,
      dedicatedCsAtAssign: null,
      dedicatedCsLastVisitAt: null,
      priorityScoreAtAssign: null,
      selectionMode: null,
      assignmentId: 'b1',
      status: 'assigned',
    }).explainAssignment(SCOPE, 'p1');
    expect(String(r['怎么落到他头上的'])).toContain('没有记录');
    expect(String(r['分配当时的优先级分'])).toContain('没有记录');
    expect(String(r['怎么被选进这批的'])).toContain('没有记录');
  });
});
