import { Test } from '@nestjs/testing';
import { PlanService } from '../src/modules/plan/plan.service';
import { PrismaService } from '../src/prisma/prisma.service';
import { Permission } from '@pac/types';

/**
 * 列表页「按患者过滤」的回归 —— 姓名搜索 / 真号码 / 画像圈人。
 *
 * ── 事故经过(2026-08-06)──────────────────────────────────────────
 * 这三个筛选条件都先攒进一个**局部对象** `patientWhere`,最后由一行
 *   `where.patient = patientWhere`
 * 挂到查询上。换初选口径(温度改从召回证据算)时,旧的 Prisma 温度谓词块
 * 紧贴在这一行前面,删旧块时**把它一起删了**。
 *
 * 🔴 后果是三个筛选**同时静默失效**:搜「黄」照样返回全部 1729 人 ——
 *   不报错、不空列表,看起来只是"没搜着这个人"。走查时才被发现。
 *
 * ⚠️ 这类 bug 的形状:**攒条件的地方和生效的地方隔着几十行**,
 *   中间任何一次删改都可能把出口带走,而类型系统看不见(局部对象没人读不算错)。
 *   ⇒ 只能靠这条测试盯着"出口还在不在"。
 */

const HOST = 'h1';
const TENANT = 't1';
const PERMS = [Permission.PLAN_VIEW_ALL, Permission.PLAN_DISPATCH];

function makePrisma() {
  const calls: Array<Record<string, unknown>> = [];
  const prisma = {
    followupPlan: {
      findMany: jest.fn(async (args: Record<string, unknown>) => {
        calls.push(args);
        return [];
      }),
      count: jest.fn(async () => 0),
    },
  } as unknown as PrismaService;
  return { prisma, calls };
}

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

/** 取 findMany 实际用的 where.patient(没挂上就是 undefined —— 正是那个 bug) */
async function patientWhereOf(query: Record<string, unknown>, sourceUnits: string[] = []) {
  const { prisma, calls } = makePrisma();
  const svc = await build(prisma);
  const scope = {
    hostId: HOST,
    tenantId: TENANT,
    sourceUnits,
    clinicIds: [] as string[],
    userId: 'u1',
  } as never;
  await svc.list(scope, { view: 'pool', ...query } as never, PERMS);
  const where = calls[0]?.where as Record<string, unknown> | undefined;
  return where?.patient as Record<string, unknown> | undefined;
}

describe('列表页按患者过滤 —— 条件必须真的挂到查询上', () => {
  test('⭐⭐ 姓名搜索:keyword 要落到 where.patient.OR(挂不上就是"搜了等于没搜")', async () => {
    const p = await patientWhereOf({ keyword: '黄' });
    expect(p).toBeDefined(); // 🔴 bug 时这里是 undefined
    const or = p!.OR as Array<Record<string, Record<string, unknown>>>;
    expect(or.map((c) => Object.keys(c)[0])).toEqual([
      'name',
      'phone',
      'medicalRecordNumber',
      'externalId',
    ]);
    expect(or[0]!.name!.contains).toBe('黄');
  });

  test('⭐ 真号码筛选同样要挂上', async () => {
    const p = await patientWhereOf({ phoneVerified: true });
    expect(p?.phoneVerified).toBe(true);
  });

  test('⭐ 画像圈人同样要挂上', async () => {
    const p = await patientWhereOf({ personaTags: 'gender:male' });
    expect(Array.isArray(p?.AND)).toBe(true);
    expect((p!.AND as unknown[]).length).toBeGreaterThan(0);
  });

  test('⭐⭐ 挂的时候⛔不能盖掉 scope 的品牌边界(sourceUnit)—— 那是越权', async () => {
    // 原实现是直接赋值 `where.patient = patientWhere`,会把顶上那句
    // `patient: { sourceUnit: { in: scope.sourceUnits } }` **整个覆盖掉**。
    const p = await patientWhereOf({ keyword: '黄' }, ['ruier']);
    expect(p?.sourceUnit).toEqual({ in: ['ruier'] }); // 边界还在
    expect(p?.OR).toBeDefined(); // 搜索也在
  });

  test('什么都不传时不挂 patient 条件(别凭空加一个空对象)', async () => {
    const p = await patientWhereOf({});
    expect(p).toBeUndefined();
  });
});
