import { Test } from '@nestjs/testing';
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { ProductFamiliesService } from './product-families.service';
import { FamilyRecomputeService } from './family-recompute.service';
import { PrismaService } from '../prisma/prisma.service';

describe('ProductFamiliesService', () => {
  let service: ProductFamiliesService;
  let prisma: PrismaService;
  const stamp = Date.now();
  const createdOriginGoodIds: bigint[] = [];
  const createdFamilyIds: bigint[] = [];

  const mkOriginGood = async (name: string, over: Record<string, unknown> = {}) => {
    const og = await prisma.originGood.create({
      data: {
        sdsGoodId: `famsvc-${stamp}-${createdOriginGoodIds.length}-${Math.random().toString(36).slice(2, 7)}`,
        goodName: name,
        ...over,
      },
    });
    createdOriginGoodIds.push(og.id);
    return og;
  };

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      providers: [ProductFamiliesService, FamilyRecomputeService, PrismaService],
    }).compile();
    service = moduleRef.get(ProductFamiliesService);
    prisma = moduleRef.get(PrismaService);
    await prisma.onModuleInit();
  });

  afterAll(async () => {
    await prisma.originGood.deleteMany({ where: { id: { in: createdOriginGoodIds } } });
    await prisma.productFamily.deleteMany({ where: { id: { in: createdFamilyIds } } });
    await prisma.$disconnect();
  });

  it('create：挂成员、重算、familyCode 冲突自动加后缀', async () => {
    const code = `DG${stamp}T`;
    const a = await mkOriginGood('美国（包邮）T恤-DGTEST-单面印花', {
      craftLabel: '单面印花',
      logisticsLabel: '包邮',
    });
    await prisma.originGoodVariant.create({
      data: {
        originGoodId: a.id,
        sdsVariantId: 'svc-v1',
        sku: 'T-S',
        sizeId: 'size_S',
        sizeName: 'S',
        colorId: 'color_blk',
        colorName: '黑色',
        price: new Prisma.Decimal(25),
      },
    });
    const f1 = (await service.create({
      familyName: '测试T恤',
      familyCode: code,
      originGoodIds: [a.id.toString()],
      primaryOriginGoodId: a.id.toString(),
    })) as any;
    createdFamilyIds.push(BigInt(f1.id));
    expect(f1.familyCode).toBe(code);
    expect(f1._count.originGoods).toBe(1);
    expect((f1.priceMatrix as any).rows).toHaveLength(1);

    const f2 = (await service.create({ familyName: '测试T恤二号', familyCode: code })) as any;
    createdFamilyIds.push(BigInt(f2.id));
    expect(f2.familyCode).toBe(`${code}-2`);
  });

  it('detail：不存在 404', async () => {
    await expect(service.detail(999999n)).rejects.toThrow(NotFoundException);
  });

  it('list：keyword 过滤 familyName/familyCode + 分页字段', async () => {
    const code = `DG${stamp}T`;
    const res = (await service.list({ keyword: `测试T恤`, page: 1, pageSize: 10 })) as any;
    expect(res.total).toBeGreaterThanOrEqual(2);
    expect(res.items.length).toBeGreaterThanOrEqual(2);
    expect(res.page).toBe(1);
    const byCode = (await service.list({ keyword: `${code}-2` })) as any;
    expect(byCode.total).toBe(1);
  });

  it('patch：改 canonical 字段并触发重算，不丢成员', async () => {
    const f = (await service.create({ familyName: `补丁族-${stamp}` })) as any;
    createdFamilyIds.push(BigInt(f.id));
    const patched = (await service.patch(BigInt(f.id), {
      familyName: `补丁族改-${stamp}`,
      autoManaged: false,
    })) as any;
    expect(patched.familyName).toBe(`补丁族改-${stamp}`);
    expect(patched.autoManaged).toBe(false);
    // 改回，避免影响后续用例
    await service.patch(BigInt(f.id), { autoManaged: true });
  });

  it('auto-group：无分类时按名称键分组；预览不写库；apply 幂等', async () => {
    const g1a = await mkOriginGood(`自动组${stamp}（包邮）卫衣-ZZ${stamp}-单面印花`);
    const g1b = await mkOriginGood(`自动组${stamp}（包邮）卫衣-ZZ${stamp}-单面印花-某仓`);
    const g2 = await mkOriginGood(`自动组${stamp}（不包邮）卫衣-ZZ${stamp}-单面印花`);

    const preview = (await service.autoGroup(false)) as any;
    expect(preview.applied).toBe(0);
    const hit = preview.groups.find((g: any) => g.groupKey.includes(`ZZ${stamp}`) && g.memberCount === 2);
    expect(hit).toBeTruthy();
    expect(hit.familyName).toContain('卫衣');

    const applied = (await service.autoGroup(true)) as any;
    expect(applied.applied).toBeGreaterThanOrEqual(2); // 包邮组 + 不包邮组（物流不同不同组）
    const families = await prisma.productFamily.findMany({
      where: { familyName: { contains: `自动组${stamp}` } },
    });
    for (const f of families) createdFamilyIds.push(f.id);
    const grouped = await prisma.originGood.findMany({
      where: { id: { in: [g1a.id, g1b.id, g2.id] } },
      select: { familyId: true },
    });
    expect(grouped.every((g) => g.familyId !== null)).toBe(true);
    expect(grouped[0].familyId).toBe(grouped[1].familyId); // 同组同族
    expect(grouped[2].familyId).not.toBe(grouped[0].familyId); // 物流不同不同族

    // 幂等：候选已清空
    const again = (await service.autoGroup(true)) as any;
    const hitAgain = again.groups.filter((g: any) => g.groupKey.includes(`ZZ${stamp}`));
    expect(hitAgain).toHaveLength(0);
  });

  it('auto-group：同 SDS 分类（产品模型）跨工艺/编码归一族，族名取分类名', async () => {
    const cat = await prisma.category.create({
      data: { categoryName: `ZZF${stamp} 180G纯棉T恤（ZZA${stamp}）`, sdsCategoryId: `cat-hook-${stamp}` },
    });
    try {
      const a = await mkOriginGood(`美国（包邮）180g纯棉T恤-DGZ${stamp}-单面印花`, {
        sdsCategoryId: `cat-hook-${stamp}`,
        craftLabel: '单面印花',
        logisticsLabel: '包邮',
      });
      const b = await mkOriginGood(`美国（不包邮）180GT恤-ZZA${stamp}-双面印花-某仓`, {
        sdsCategoryId: `cat-hook-${stamp}`,
        craftLabel: '双面印花',
        logisticsLabel: '不包邮',
      });
      const result = (await service.autoGroup(true)) as any;
      const grouped = await prisma.originGood.findMany({
        where: { id: { in: [a.id, b.id] } },
        select: { familyId: true },
      });
      expect(grouped[0].familyId).not.toBeNull();
      expect(grouped[0].familyId).toBe(grouped[1].familyId); // 跨工艺/编码/物流同族
      const family = await prisma.productFamily.findUniqueOrThrow({
        where: { id: grouped[0].familyId! },
      });
      createdFamilyIds.push(family.id);
      expect(family.familyName).toBe(`ZZF${stamp} 180G纯棉T恤（ZZA${stamp}）`);
      expect(family.familyCode).toBe(`ZZF${stamp}`);
      expect(result.applied).toBeGreaterThanOrEqual(1);
    } finally {
      await prisma.category.delete({ where: { id: cat.id } }).catch(() => undefined);
    }
  });

  it('members：增删成员、移除主链接后 primary 落到剩余成员', async () => {
    const a = await mkOriginGood(`成员${stamp}A`, { craftLabel: '单面印花', logisticsLabel: '包邮' });
    const b = await mkOriginGood(`成员${stamp}B`, { craftLabel: '单面印花', logisticsLabel: '包邮' });
    const c = await mkOriginGood(`成员${stamp}C`, { craftLabel: '单面印花', logisticsLabel: '包邮' });
    const f = (await service.create({
      familyName: `成员族-${stamp}`,
      originGoodIds: [a.id.toString(), b.id.toString(), c.id.toString()],
      primaryOriginGoodId: a.id.toString(),
    })) as any;
    createdFamilyIds.push(BigInt(f.id));

    const removed = (await service.updateMembers(BigInt(f.id), {
      removeOriginGoodIds: [a.id.toString()],
    })) as any;
    expect(removed._count.originGoods).toBe(2);
    expect(BigInt(removed.primaryOriginGoodId)).toBe(b.id); // 落到剩余最小 id

    const added = (await service.updateMembers(BigInt(f.id), {
      addOriginGoodIds: [a.id.toString()],
    })) as any;
    expect(added._count.originGoods).toBe(3);

    await expect(
      service.updateMembers(BigInt(f.id), { addOriginGoodIds: ['999999'] }),
    ).rejects.toThrow(BadRequestException);
  });

  it('custom member：创建带归因的 CUSTOM 成员并入矩阵', async () => {
    const f = (await service.create({ familyName: `自建族-${stamp}` })) as any;
    createdFamilyIds.push(BigInt(f.id));
    const member = (await service.createCustomMember(BigInt(f.id), {
      goodName: `自建商品-${stamp}`,
      logisticsLabel: '海运',
      craftLabel: '双面印花',
      skuCode: `ZZC${stamp}`,
      variants: [{ sku: 'C-M', sizeId: 'size_M', sizeName: 'M', colorId: 'color_red', colorName: '红色', price: 33 }],
      detail: { sizeChart: { rows: [{ sizeId: 'size_M', sizeName: 'M', chest: 100 }] } },
    })) as any;
    expect(member.sdsGoodId.startsWith('custom-')).toBe(true);
    expect(member.logisticsLabel).toBe('海运');

    const after = await prisma.productFamily.findUniqueOrThrow({ where: { id: BigInt(f.id) } });
    const matrix = after.priceMatrix as any;
    expect(matrix.rows).toHaveLength(1);
    expect(matrix.rows[0].price).toBe('33');
    expect(matrix.logistics).toContain('海运');
    expect((after.sizeChart as any).rows).toHaveLength(1); // 自定义成员尺码参与并集
    expect(BigInt(after.primaryOriginGoodId!)).toBe(BigInt(member.id)); // 空族首个成员成为主链接
  });

  it('price overrides：非法维度 400；合法覆盖生效；删除恢复推导价', async () => {
    const a = await mkOriginGood(`覆盖${stamp}`, { craftLabel: '单面印花', logisticsLabel: '包邮' });
    await prisma.originGoodVariant.create({
      data: {
        originGoodId: a.id,
        sdsVariantId: 'ov-v1',
        sku: 'O-S',
        sizeId: 'size_S',
        sizeName: 'S',
        colorId: 'color_blk',
        colorName: '黑色',
        price: new Prisma.Decimal(25),
      },
    });
    const f = (await service.create({
      familyName: `覆盖族-${stamp}`,
      originGoodIds: [a.id.toString()],
      primaryOriginGoodId: a.id.toString(),
    })) as any;
    createdFamilyIds.push(BigInt(f.id));
    const fid = BigInt(f.id);

    await expect(
      service.putPriceOverrides(fid, [
        { sizeId: 'size_S', colorId: 'color_blk', craft: '不存在的工艺', logistics: '包邮', price: 1 },
      ]),
    ).rejects.toThrow(BadRequestException);

    const result = (await service.putPriceOverrides(fid, [
      { sizeId: 'size_S', colorId: 'color_blk', craft: '单面印花', logistics: '包邮', price: 23, note: '促销' },
    ])) as any;
    expect(result.items).toHaveLength(1);
    expect(result.items[0].derivedPrice).toBe('25');
    expect(result.items[0].diff).toBe('-2.00');

    const after = await prisma.productFamily.findUniqueOrThrow({ where: { id: fid } });
    const row = (after.priceMatrix as any).rows[0];
    expect(row.price).toBe('23');
    expect(row.manual).toBe(true);

    const restored = (await service.deletePriceOverrides(fid, {
      cells: [{ sizeId: 'size_S', colorId: 'color_blk', craft: '单面印花', logistics: '包邮' }],
    })) as any;
    expect(restored.items).toHaveLength(0);
    const after2 = await prisma.productFamily.findUniqueOrThrow({ where: { id: fid } });
    expect((after2.priceMatrix as any).rows[0].price).toBe('25');
    expect((after2.priceMatrix as any).rows[0].manual).toBe(false);
  });

  it('recomputeNow：手动重算返回最新族详情', async () => {
    const f = (await service.create({ familyName: `手动重算族-${stamp}` })) as any;
    createdFamilyIds.push(BigInt(f.id));
    const res = (await service.recomputeNow(BigInt(f.id))) as any;
    expect(BigInt(res.id)).toBe(BigInt(f.id));
    await expect(service.recomputeNow(999999n)).rejects.toThrow(NotFoundException);
  });
});
