import { Test } from '@nestjs/testing';
import { ConflictException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { TagsService } from './tags.service';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTagDto } from './dto/create-tag.dto';

describe('TagsService', () => {
  let service: TagsService;
  let prisma: PrismaService;
  const createdNames: string[] = [];

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

  afterAll(async () => {
    if (createdNames.length) {
      await prisma.tag.deleteMany({ where: { tagName: { in: createdNames } } });
    }
    await prisma.onModuleDestroy();
  });

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

  it('creates a tag and validates hex color via DTO', async () => {
    const name = `Tag ${Date.now()}`;
    createdNames.push(name);
    const created = await service.create({ tagName: name, tagColor: '#FF8800' });
    expect(created.tagColor).toBe('#FF8800');

    const invalid = plainToInstance(CreateTagDto, {
      tagName: 'invalid',
      tagColor: 'not-a-color',
    });
    const errors = await validate(invalid);
    expect(errors.length).toBeGreaterThan(0);
    expect(errors[0].property).toBe('tagColor');
  });

  it('rejects duplicate tag names with ConflictException', async () => {
    const name = `Dup ${Date.now()}`;
    createdNames.push(name);
    await service.create({ tagName: name });
    await expect(service.create({ tagName: name })).rejects.toBeInstanceOf(
      ConflictException,
    );
  });

  it('updates and deletes', async () => {
    const name = `Upd ${Date.now()}`;
    createdNames.push(name);
    const tag = await service.create({ tagName: name });
    const updated = await service.update(tag.id, { tagName: `${name}-v2` });
    expect(updated.tagName).toBe(`${name}-v2`);
    await service.remove(tag.id);
    const idx = createdNames.indexOf(name);
    if (idx !== -1) createdNames[idx] = `${name}-v2`;
  });

  it('assigns tagGroupId and sortOrder on create, supports reordering', async () => {
    // Use existing seeded groups
    const group = await prisma.tagGroup.findFirst({
      where: { groupName: '物流渠道' },
    });
    expect(group).not.toBeNull();

    const a = `Grouped A ${Date.now()}`;
    const b = `Grouped B ${Date.now()}`;
    createdNames.push(a, b);

    const tagA = await service.create({
      tagName: a,
      tagGroupId: Number(group!.id),
      sortOrder: 1,
    });
    expect(tagA.tagGroupId).toBe(group!.id);

    const tagB = await service.create({
      tagName: b,
      tagGroupId: Number(group!.id),
      sortOrder: 2,
    });
    expect(tagB.sortOrder).toBe(2);

    // Reorder swap
    await service.reorder({
      items: [
        { id: Number(tagA.id), sortOrder: 2 },
        { id: Number(tagB.id), sortOrder: 1 },
      ],
    });

    const refreshedA = await prisma.tag.findUnique({ where: { id: tagA.id } });
    const refreshedB = await prisma.tag.findUnique({ where: { id: tagB.id } });
    expect(refreshedA!.sortOrder).toBe(2);
    expect(refreshedB!.sortOrder).toBe(1);
  });

  it('removes a tag cleanly even when it is in a group', async () => {
    const group = await prisma.tagGroup.findFirst({
      where: { groupName: '印刷工艺' },
    });
    const name = `Grouped ${Date.now()}`;
    createdNames.push(name);

    const tag = await service.create({
      tagName: name,
      tagGroupId: Number(group!.id),
    });
    await service.remove(tag.id);
    const found = await prisma.tag.findUnique({ where: { id: tag.id } });
    expect(found).toBeNull();
  });
});
