import { Test } from '@nestjs/testing';
import {
  BadRequestException,
  NotFoundException,
} from '@nestjs/common';
import { GoodsService } from './goods.service';
import { PrismaService } from '../prisma/prisma.service';

describe('GoodsService', () => {
  let service: GoodsService;
  let prisma: PrismaService;
  const stamp = Date.now();

  // Fixtures
  let countryId: bigint;
  let country2Id: bigint;
  let categoryId: bigint;
  let childCategoryId: bigint;
  let tagId: bigint;
  let positionId: bigint;
  let originGoodIds: bigint[] = [];

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

    const country = await prisma.country.create({
      data: { countryName: `Goods Country ${stamp}` },
    });
    countryId = country.id;
    const country2 = await prisma.country.create({
      data: { countryName: `Goods Country 2 ${stamp}` },
    });
    country2Id = country2.id;

    const cat = await prisma.category.create({
      data: { categoryName: `Goods Cat ${stamp}` },
    });
    categoryId = cat.id;
    const child = await prisma.category.create({
      data: { categoryName: `Goods Child ${stamp}`, parentCategoryId: cat.id },
    });
    childCategoryId = child.id;

    const tag = await prisma.tag.create({
      data: { tagName: `Goods Tag ${stamp}`, tagColor: '#00FF00' },
    });
    tagId = tag.id;

    const pos = await prisma.position.create({
      data: { indexVal: 1, countryId, categoryId },
    });
    positionId = pos.id;

    const originGoods = await Promise.all(
      Array.from({ length: 5 }).map((_, i) =>
        prisma.originGood.create({
          data: {
            sdsGoodId: `sds-goods-${stamp}-${i}`,
            goodName: `Goods Origin ${stamp} ${i}`,
          },
        }),
      ),
    );
    originGoodIds = originGoods.map((og) => og.id);
  });

  afterAll(async () => {
    // Wipe all goods first so origin_goods/category can be removed.
    await prisma.good.deleteMany({
      where: { goodName: { contains: `Goods Test ${stamp}` } },
    });
    await prisma.good.deleteMany({
      where: { goodName: { contains: `Origin ${stamp}` } },
    });
    await prisma.originGood.deleteMany({
      where: { id: { in: originGoodIds } },
    });
    await prisma.position.delete({ where: { id: positionId } });
    await prisma.tag.delete({ where: { id: tagId } });
    await prisma.category.delete({ where: { id: childCategoryId } });
    await prisma.category.delete({ where: { id: categoryId } });
    await prisma.country.delete({ where: { id: countryId } });
    await prisma.country.delete({ where: { id: country2Id } });
    await prisma.onModuleDestroy();
  });

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

  it('creates and reads back a good', async () => {
    const created = await service.create({
      goodName: `Goods Test ${stamp} basic`,
      originGoodId: Number(originGoodIds[0]),
      countryId: Number(countryId),
      categoryId: Number(categoryId),
      tagIds: [Number(tagId)],
      positionId: Number(positionId),
      goodPriority: 3,
    });
    expect(created.id).toBeTruthy();
    expect(created.country?.countryName).toBeTruthy();
    expect(created.tags.some((t) => t.tagColor === '#00FF00')).toBe(true);

    const fetched = await service.findOne(BigInt(created.id));
    expect(fetched.goodName).toBe(`Goods Test ${stamp} basic`);
  });

  it('filters by countryId, tagId, positionId and keyword', async () => {
    const result = await service.findAll({
      page: 1,
      pageSize: 20,
      countryId: Number(countryId),
      tagId: Number(tagId),
      keyword: `Goods Test ${stamp}`,
    });
    expect(result.items.length).toBeGreaterThan(0);
    expect(result.items.every((g) => g.countryId === countryId.toString())).toBe(true);
    expect(
      result.items.every((g) => g.tags.some((t) => t.id === tagId.toString())),
    ).toBe(true);
  });

  it('categoryId filter includes descendants recursively', async () => {
    const inChild = await service.create({
      goodName: `Goods Test ${stamp} child`,
      originGoodId: Number(originGoodIds[1]),
      countryId: Number(countryId),
      categoryId: Number(childCategoryId),
    });
    const result = await service.findAll({
      page: 1,
      pageSize: 20,
      categoryId: Number(categoryId),
      keyword: `Goods Test ${stamp}`,
    });
    const ids = result.items.map((g) => g.id);
    expect(ids).toContain(inChild.id);
  });

  it('batch update priority is atomic', async () => {
    const created = await service.create({
      goodName: `Goods Test ${stamp} prio`,
      originGoodId: Number(originGoodIds[2]),
      countryId: Number(countryId),
      categoryId: Number(categoryId),
    });
    const result = await service.batchUpdatePriority({
      items: [{ id: Number(created.id), priority: 42 }],
    });
    expect(result.count).toBe(1);
    const after = await service.findOne(BigInt(created.id));
    expect(after.goodPriority).toBe(42);
  });

  it('batch create creates all rows or none', async () => {
    // Count of goods whose originGoodId is one of the two fixture ids,
    // so we are independent of goodName (which batch derives from origin).
    const before = await service.findAll({
      page: 1,
      pageSize: 100,
      keyword: `Goods Origin ${stamp}`,
    });
    const created = await service.batchCreate({
      countryId: Number(countryId),
      categoryId: Number(categoryId),
      defaultPriority: 1,
      items: [
        { originGoodId: Number(originGoodIds[3]) },
        { originGoodId: Number(originGoodIds[4]) },
      ],
    });
    expect(created.length).toBe(2);
    const after = await service.findAll({
      page: 1,
      pageSize: 100,
      keyword: `Goods Origin ${stamp}`,
    });
    expect(after.total).toBe(before.total + 2);
  });

  it('batch create rolls back on failure', async () => {
    const before = await service.findAll({
      page: 1,
      pageSize: 100,
      keyword: `Goods Origin ${stamp}`,
    });
    await expect(
      service.batchCreate({
        countryId: Number(countryId),
        categoryId: Number(categoryId),
        items: [
          { originGoodId: Number(originGoodIds[0]) },
          { originGoodId: 99999999 }, // missing -> failure
        ],
      }),
    ).rejects.toBeInstanceOf(BadRequestException);
    const after = await service.findAll({
      page: 1,
      pageSize: 100,
      keyword: `Goods Origin ${stamp}`,
    });
    expect(after.total).toBe(before.total);
  });

  it('throws NotFoundException for unknown id', async () => {
    await expect(service.findOne(BigInt(99999999))).rejects.toBeInstanceOf(
      NotFoundException,
    );
  });
});
