import { RequirementService } from './requirement.service';

describe('RequirementService with V2.2 composite requirement key', () => {
  const makeService = () => {
    const prisma = {
      requirement: {
        create: jest.fn(),
        delete: jest.fn(),
        findFirst: jest.fn(),
        findMany: jest.fn(),
        update: jest.fn(),
      },
    };

    return {
      prisma,
      service: new RequirementService(prisma as any),
    };
  };

  it('creates requirements with a partition-key-scoped business code', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.create.mockResolvedValue({ id: 'req-1', productId: 'product-1', code: 'REQ-001' });

    await service.create('product-1', {
      code: 'REQ-001',
      title: 'Payment',
      creatorId: 'user-1',
    });

    expect(prisma.requirement.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        productId: 'product-1',
        code: 'REQ-001',
        title: 'Payment',
      }),
    });
  });

  it('creates requirements with frontend pool fields in relation columns', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.create.mockResolvedValue({ id: 'req-1', productId: 'product-1', code: 'REQ-001' });

    await service.create('product-1', {
      code: 'REQ-001',
      title: 'Payment',
      projectId: 'project-1',
      versionId: 'version-1',
      type: 'feature',
      sourceType: 'customer',
      sourceTarget: 'ACME',
      platform: 'web,ios',
      priority: 1,
    } as any);

    expect(prisma.requirement.create).toHaveBeenCalledWith({
      data: expect.objectContaining({
        productId: 'product-1',
        projectId: 'project-1',
        versionId: 'version-1',
        type: 'feature',
        sourceType: 'customer',
        sourceTarget: 'ACME',
        platform: 'web,ios',
      }),
    });
  });

  it('paginates requirement pool queries by product partition key and composite cursor', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findMany.mockResolvedValue([{ id: 'req-2' }, { id: 'req-1' }]);

    const result = await service.findAll('product-1', {
      projectId: 'project-1',
      versionId: 'version-1',
      status: 'adopted',
      priority: 'P1',
      type: 'feature',
      q: 'login',
      sort: 'created_at_asc',
      cursor: 'req-3',
      limit: '1',
    } as any);

    expect(result).toEqual({ items: [{ id: 'req-2' }], nextCursor: 'req-1' });
    expect(prisma.requirement.findMany).toHaveBeenCalledWith({
      where: {
        productId: 'product-1',
        projectId: 'project-1',
        versionId: 'version-1',
        status: 'adopted',
        priority: 1,
        type: 'feature',
        OR: [
          { code: { contains: 'login', mode: 'insensitive' } },
          { title: { contains: 'login', mode: 'insensitive' } },
        ],
      },
      include: { creator: { select: { id: true, name: true } } },
      orderBy: { createdAt: 'asc' },
      take: 2,
      cursor: { id_productId: { id: 'req-3', productId: 'product-1' } },
      skip: 1,
    });
  });

  it('uses only approved requirement sort keys', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findMany.mockResolvedValue([]);

    await service.findAll('product-1', { sort: 'unsafe_sql_asc', limit: '20' } as any);

    expect(prisma.requirement.findMany).toHaveBeenCalledWith(expect.objectContaining({
      orderBy: { createdAt: 'desc' },
    }));
  });

  it('updates requirements by id plus product id', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findFirst.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: 'pending_review',
    });
    prisma.requirement.update.mockResolvedValue({ id: 'req-1', productId: 'product-1' });

    await service.update('product-1', 'req-1', { title: 'Payment v2' });

    expect(prisma.requirement.update).toHaveBeenCalledWith({
      where: { id_productId: { id: 'req-1', productId: 'product-1' } },
      data: { title: 'Payment v2' },
    });
  });

  it('updates frontend pool fields by id plus product id', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findFirst.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: 'pending_review',
    });
    prisma.requirement.update.mockResolvedValue({ id: 'req-1', productId: 'product-1' });

    await service.update('product-1', 'req-1', {
      projectId: 'project-2',
      versionId: 'version-2',
      type: 'optimization',
      sourceType: 'internal',
      sourceTarget: 'Product',
      platform: 'web,h5',
    } as any);

    expect(prisma.requirement.update).toHaveBeenCalledWith({
      where: { id_productId: { id: 'req-1', productId: 'product-1' } },
      data: {
        projectId: 'project-2',
        versionId: 'version-2',
        type: 'optimization',
        sourceType: 'internal',
        sourceTarget: 'Product',
        platform: 'web,h5',
      },
    });
  });

  it('deletes requirements by id plus product id', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findFirst.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: 'pending_review',
    });
    prisma.requirement.delete.mockResolvedValue({ id: 'req-1', productId: 'product-1' });

    await service.remove('product-1', 'req-1');

    expect(prisma.requirement.delete).toHaveBeenCalledWith({
      where: { id_productId: { id: 'req-1', productId: 'product-1' } },
    });
  });

  it.each([
    ['pending_review', 'adopted'],
    ['pending_review', 'rejected'],
    ['rejected', 'pending_review'],
    ['adopted', 'planned'],
    ['adopted', 'closed'],
    ['planned', 'developing'],
    ['planned', 'closed'],
    ['developing', 'testing'],
    ['testing', 'released'],
    ['released', 'closed'],
  ])('allows current requirement workflow transition %s -> %s', async (fromStatus, toStatus) => {
    const { prisma, service } = makeService();
    prisma.requirement.findFirst.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: fromStatus,
    });
    prisma.requirement.update.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: toStatus,
    });

    await service.updateStatus('product-1', 'req-1', toStatus);

    expect(prisma.requirement.update).toHaveBeenCalledWith({
      where: { id_productId: { id: 'req-1', productId: 'product-1' } },
      data: { status: toStatus },
    });
  });

  it('rejects skipping adopted requirements directly to development', async () => {
    const { prisma, service } = makeService();
    prisma.requirement.findFirst.mockResolvedValue({
      id: 'req-1',
      productId: 'product-1',
      status: 'adopted',
    });

    await expect(service.updateStatus('product-1', 'req-1', 'developing')).rejects.toThrow(
      '无法从 "adopted" 转换到 "developing"',
    );
    expect(prisma.requirement.update).not.toHaveBeenCalled();
  });
});
