import { Test } from '@nestjs/testing';
import { PriorityDistributionService } from '../src/modules/plan/priority-distribution.service';
import { PrismaService } from '../src/prisma/prisma.service';

/**
 * 召回池优先级分位回归。
 *
 * 由来:hover 把三维拆解讲得很细,客服看完仍不知道**这分算高还是低** —— 看到 9.78/10
 * 会以为"还行"。生产实测分布完全不是这样(active+assigned 23.6 万):
 *   p50=31 · p80=56 · p90=66 · p95=76 · p99=82
 * 97.8 分其实是**前 0.05%**,是整池最该先打的。没有参照系这条信息就丢了。
 *
 * 本文件锁三条:分位算得对、池太小时不给(免误导)、分布查询按租户缓存且不打惊群。
 */

/** 造一个分布:scores 里每个分值一条 plan */
function makePrisma(scores: number[]) {
  const calls = { queries: 0 };
  const grouped = new Map<number, number>();
  for (const s of scores) grouped.set(s, (grouped.get(s) ?? 0) + 1);
  const prisma = {
    $queryRaw: jest.fn().mockImplementation(async () => {
      calls.queries++;
      return [...grouped.entries()].map(([s, n]) => ({ s, n: BigInt(n) }));
    }),
  };
  return { prisma: prisma as unknown as PrismaService, calls };
}

async function build(scores: number[]) {
  const { prisma, calls } = makePrisma(scores);
  const mod = await Test.createTestingModule({
    providers: [PriorityDistributionService, { provide: PrismaService, useValue: prisma }],
  }).compile();
  return { svc: mod.get(PriorityDistributionService), calls, prisma };
}

/** 0..99 各一条 → 分数 n 的分位就是 n */
const UNIFORM = Array.from({ length: 100 }, (_, i) => i);

describe('percentileOf — 分位算得对', () => {
  test('⭐ 均匀分布:分位 = 严格低于它的占比', async () => {
    const { svc } = await build(UNIFORM);
    expect(await svc.percentileOf('h', 't', 0)).toBe(0);
    expect(await svc.percentileOf('h', 't', 50)).toBe(50);
    expect(await svc.percentileOf('h', 't', 99)).toBe(99);
  });

  test('⭐ 生产形态(长尾):高分确实落在最顶端', async () => {
    // 90% 集中在 20-40,10% 散在 60-100 —— 近似生产
    const scores = [
      ...Array.from({ length: 900 }, (_, i) => 20 + (i % 21)),
      ...Array.from({ length: 100 }, (_, i) => 60 + (i % 41)),
    ];
    const { svc } = await build(scores);
    expect(await svc.percentileOf('h', 't', 31)).toBeLessThan(60); // 中位附近
    expect(await svc.percentileOf('h', 't', 98)).toBeGreaterThanOrEqual(99); // 前 1%
  });

  test('分数越界不炸(<0 / >100 夹到边界)', async () => {
    const { svc } = await build(UNIFORM);
    expect(await svc.percentileOf('h', 't', -5)).toBe(0);
    expect(await svc.percentileOf('h', 't', 999)).toBe(100);
  });

  test('⭐ 池太小(<50)→ null,不给会误导的分位', async () => {
    const { svc } = await build([10, 20, 30]);
    expect(await svc.percentileOf('h', 't', 30)).toBeNull();
  });

  test('空池 → null', async () => {
    const { svc } = await build([]);
    expect(await svc.percentileOf('h', 't', 50)).toBeNull();
  });

  test('⭐ 查询失败不拖垮列表 —— 返回 null 而不是抛', async () => {
    const prisma = { $queryRaw: jest.fn().mockRejectedValue(new Error('db down')) };
    const mod = await Test.createTestingModule({
      providers: [PriorityDistributionService, { provide: PrismaService, useValue: prisma as unknown as PrismaService }],
    }).compile();
    await expect(mod.get(PriorityDistributionService).percentileOf('h', 't', 50)).resolves.toBeNull();
  });
});

describe('缓存与防惊群', () => {
  test('⭐ 同租户连续取分位只查一次分布(列表每行都要算,不能每行一查)', async () => {
    const { svc, calls } = await build(UNIFORM);
    for (let i = 0; i < 20; i++) await svc.percentileOf('h', 't', i);
    expect(calls.queries).toBe(1);
  });

  test('⭐ 并发首取共享同一个在飞查询(single-flight)', async () => {
    const { svc, calls } = await build(UNIFORM);
    await Promise.all(Array.from({ length: 20 }, (_, i) => svc.percentileOf('h', 't', i)));
    expect(calls.queries).toBe(1);
  });

  test('不同租户各自缓存,不串味', async () => {
    const { svc, calls } = await build(UNIFORM);
    await svc.percentileOf('h', 't1', 50);
    await svc.percentileOf('h', 't2', 50);
    expect(calls.queries).toBe(2);
  });
});
