import { BadRequestException, NotFoundException } from '@nestjs/common';
import { V22QueryService } from './v22-query.service';

function buildPrismaMock() {
  return {
    version: {
      findUnique: jest.fn(),
    },
    requirement: {
      findMany: jest.fn(),
    },
    versionPlan: {
      findMany: jest.fn(),
    },
    devTask: {
      findMany: jest.fn(),
    },
    testCase: {
      findMany: jest.fn(),
    },
    bug: {
      findMany: jest.fn(),
    },
    xiaobaoRiskSummary: {
      findMany: jest.fn(),
    },
  };
}

describe('V22QueryService', () => {
  it('loads version detail data through version-scoped relation queries', async () => {
    const prisma = buildPrismaMock();
    prisma.version.findUnique.mockResolvedValue({ id: 'version-1', productId: 'product-1', projectId: 'project-1' });
    prisma.requirement.findMany.mockResolvedValue([{ id: 'req-1' }]);
    prisma.versionPlan.findMany.mockResolvedValue([{ id: 'plan-1' }]);
    prisma.devTask.findMany.mockResolvedValue([{ id: 'dev-1' }]);
    prisma.testCase.findMany.mockResolvedValue([{ id: 'tc-1' }]);
    prisma.bug.findMany.mockResolvedValue([{ id: 'bug-1' }]);
    const service = new V22QueryService(prisma as any);

    const result = await service.getVersionDetailData('version-1');

    expect(result.requirements).toEqual([{ id: 'req-1' }]);
    expect(result.devTasks).toEqual([{ id: 'dev-1' }]);
    expect(prisma.requirement.findMany).toHaveBeenCalledWith({
      where: { versionId: 'version-1' },
      orderBy: { createdAt: 'desc' },
    });
    expect(prisma.devTask.findMany).toHaveBeenCalledWith({
      where: { versionId: 'version-1' },
      orderBy: [{ status: 'asc' }, { updatedAt: 'desc' }],
    });
  });

  it('rejects missing productId for requirement pool queries to avoid full-table scans', async () => {
    const service = new V22QueryService(buildPrismaMock() as any);

    await expect(service.listRequirements({})).rejects.toBeInstanceOf(BadRequestException);
  });

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

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

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

  it('aggregates workspace rows by current user without scanning unrelated assignees', async () => {
    const prisma = buildPrismaMock();
    prisma.versionPlan.findMany.mockResolvedValue([{ id: 'plan-1' }]);
    prisma.devTask.findMany.mockResolvedValue([{ id: 'dev-1' }]);
    prisma.testCase.findMany.mockResolvedValue([{ id: 'tc-1' }]);
    prisma.bug.findMany.mockResolvedValue([{ id: 'bug-1' }]);
    const service = new V22QueryService(prisma as any);

    const result = await service.getWorkspaceData('member-1');

    expect(result).toEqual({
      versionPlans: [{ id: 'plan-1' }],
      devTasks: [{ id: 'dev-1' }],
      testCases: [{ id: 'tc-1' }],
      bugs: [{ id: 'bug-1' }],
    });
    expect(prisma.devTask.findMany).toHaveBeenCalledWith({
      where: { assigneeId: 'member-1', status: { not: 'submitted' } },
      orderBy: [{ priority: 'asc' }, { updatedAt: 'desc' }],
    });
    expect(prisma.bug.findMany).toHaveBeenCalledWith({
      where: { assigneeId: 'member-1', status: { in: ['open', 'fixing', 'fixed', 'verifying'] } },
      orderBy: [{ priority: 'asc' }, { updatedAt: 'desc' }],
    });
  });

  it('loads Xiaobao warning summaries either globally for managers or by user-owned version ids', async () => {
    const prisma = buildPrismaMock();
    prisma.versionPlan.findMany.mockResolvedValue([{ versionId: 'version-1' }]);
    prisma.devTask.findMany.mockResolvedValue([{ versionId: 'version-2' }]);
    prisma.testCase.findMany.mockResolvedValue([{ versionId: 'version-2' }]);
    prisma.bug.findMany.mockResolvedValue([{ versionId: 'version-3' }]);
    prisma.xiaobaoRiskSummary.findMany.mockResolvedValue([{ versionId: 'version-2', riskScore: 70 }]);
    const service = new V22QueryService(prisma as any);

    const userResult = await service.getXiaobaoWarnings({ userId: 'member-1' });
    await service.getXiaobaoWarnings({ manager: 'true' });

    expect(userResult).toEqual([{ versionId: 'version-2', riskScore: 70 }]);
    expect(prisma.xiaobaoRiskSummary.findMany).toHaveBeenNthCalledWith(1, {
      where: {
        versionId: { in: ['version-1', 'version-2', 'version-3'] },
        riskLevel: { not: 'on_track' },
      },
      orderBy: [{ riskScore: 'desc' }, { updatedAt: 'desc' }],
    });
    expect(prisma.xiaobaoRiskSummary.findMany).toHaveBeenNthCalledWith(2, {
      where: { riskLevel: { not: 'on_track' } },
      orderBy: [{ riskScore: 'desc' }, { updatedAt: 'desc' }],
    });
  });

  it('throws when a version detail query targets a missing version', async () => {
    const prisma = buildPrismaMock();
    prisma.version.findUnique.mockResolvedValue(null);
    const service = new V22QueryService(prisma as any);

    await expect(service.getVersionDetailData('missing-version')).rejects.toBeInstanceOf(NotFoundException);
  });
});
