import { Test } from '@nestjs/testing';
import { OriginGoodsService } from './origin-goods.service';
import { FamilyRecomputeService } from '../product-families/family-recompute.service';
import { ProductFamiliesService } from '../product-families/product-families.service';
import { OrganizeService } from '../product-families/organize.service';
import { PrismaService } from '../prisma/prisma.service';

describe('OriginGoodsService', () => {
  let service: OriginGoodsService;
  let prisma: PrismaService;
  const stamp = Date.now();
  const createdSds: string[] = [];

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

    // Seed 25 rows with sequential goodNames so we can paginate/filter.
    const rows = Array.from({ length: 25 }).map((_, i) => ({
      sdsGoodId: `sds-${stamp}-${i}`,
      goodName: `Origin Good ${stamp} ${i.toString().padStart(2, '0')}`,
      sdsCategoryId: `cat-${stamp}-${i % 3}`,
    }));
    await prisma.originGood.createMany({ data: rows });
    createdSds.push(...rows.map((r) => r.sdsGoodId));
  });

  afterAll(async () => {
    if (createdSds.length) {
      await prisma.originGood.deleteMany({
        where: { sdsGoodId: { in: createdSds } },
      });
    }
    await prisma.onModuleDestroy();
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  it('findOne returns og with detail/variants payload', async () => {
    const og = await prisma.originGood.findFirstOrThrow({
      where: { sdsGoodId: `sds-${stamp}-3` },
    });
    const result = await service.findOne(og.id);
    expect(result.id).toBe(og.id.toString());
    expect(result.sdsGoodId).toBe(`sds-${stamp}-3`);
    expect(result.variantCount).toBe(0);
    expect(result.hasDetail).toBe(false);
    expect(result.detail).toBeNull();
    expect(result.variants).toEqual([]);
    await expect(service.findOne(og.id + 10_000_000n)).rejects.toThrow();
  });

  it('returns paginated results', async () => {
    const page1 = await service.findAll({
      page: 1,
      pageSize: 10,
      keyword: `Origin Good ${stamp}`,
    });
    expect(page1.total).toBe(25);
    expect(page1.items.length).toBe(10);
    expect(page1.page).toBe(1);
    expect(page1.pageSize).toBe(10);

    const page3 = await service.findAll({
      page: 3,
      pageSize: 10,
      keyword: `Origin Good ${stamp}`,
    });
    expect(page3.items.length).toBe(5);
  });

  it('searches by keyword (case insensitive)', async () => {
    const result = await service.findAll({
      page: 1,
      pageSize: 5,
      keyword: `origin good ${stamp} 05`,
    });
    expect(result.items.length).toBe(1);
    expect(result.items[0].goodName).toContain('05');
  });

  it('returns empty page when no matches', async () => {
    const result = await service.findAll({
      page: 1,
      pageSize: 5,
      keyword: 'definitely-does-not-exist',
    });
    expect(result.total).toBe(0);
    expect(result.items.length).toBe(0);
  });

  describe('getTree merged references', () => {
    /* eslint-disable @typescript-eslint/no-explicit-any */
    function findOgNode(
      treeResponse: { tree: any[] },
      ogId: string,
    ): { configuredCount: number; configuredCountries: string[] } {
      let found: { configuredCount: number; configuredCountries: string[] } | null = null;
      const walk = (nodes: any[]) => {
        for (const n of nodes) {
          const hit = (n.originGoods ?? []).find((o: any) => o.id === ogId);
          if (hit) {
            found = hit;
            return;
          }
          if (n.children?.length) walk(n.children);
        }
      };
      walk(treeResponse.tree);
      if (!found) throw new Error(`og node ${ogId} not found in tree`);
      return found;
    }

    it('counts secondary references as configured', async () => {
      const sdsCat = `tree-cat-${stamp}`;
      await prisma.originGood.createMany({
        data: [
          { sdsGoodId: `tree-a-${stamp}`, goodName: `Tree A ${stamp}`, sdsCategoryId: sdsCat },
          { sdsGoodId: `tree-b-${stamp}`, goodName: `Tree B ${stamp}`, sdsCategoryId: sdsCat },
        ],
      });
      createdSds.push(`tree-a-${stamp}`, `tree-b-${stamp}`);
      const originA = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: `tree-a-${stamp}` } });
      const originB = await prisma.originGood.findUniqueOrThrow({ where: { sdsGoodId: `tree-b-${stamp}` } });

      const cat = await prisma.category.create({
        data: { categoryName: `Tree Cat ${stamp}`, sdsCategoryId: sdsCat },
      });
      const country = await prisma.country.create({
        data: { countryName: `Tree Country ${stamp}` },
      });
      const good = await prisma.good.create({
        data: {
          goodName: `Tree Good ${stamp}`,
          originGoodId: originA.id,
          countryId: country.id,
          categoryId: cat.id,
        },
      });
      await prisma.goodOriginGood.create({
        data: { goodId: good.id, originGoodId: originB.id },
      });

      try {
        const tree = await service.getTree();
        const nodeB = findOgNode(tree, originB.id.toString());
        expect(nodeB.configuredCount).toBeGreaterThanOrEqual(1);
        expect(nodeB.configuredCountries).toContain(`Tree Country ${stamp}`);
      } finally {
        await prisma.good.delete({ where: { id: good.id } }).catch(() => undefined);
        await prisma.country.delete({ where: { id: country.id } }).catch(() => undefined);
        await prisma.category.delete({ where: { id: cat.id } }).catch(() => undefined);
      }
    });
  });
});
