import { BadRequestException } from '@nestjs/common';
import { AuditService } from '../../common/audit/audit.service';
import { RbacService } from '../../common/rbac/rbac.service';
import { GovernanceService } from './governance.service';

describe('GovernanceService', () => {
  const makeService = () => {
    const prisma = {
      taskCategory: {
        create: jest.fn(),
        delete: jest.fn(),
        findMany: jest.fn(),
        update: jest.fn(),
      },
      devTask: { count: jest.fn() },
      testCase: { count: jest.fn() },
      governanceDictionary: {
        create: jest.fn(),
        findMany: jest.fn(),
        update: jest.fn(),
        upsert: jest.fn(),
      },
    };
    const audit = { record: jest.fn() } as unknown as AuditService;
    const rbac = {
      assertGlobalPermission: jest.fn().mockResolvedValue({ actorId: 'm-admin', via: 'system' }),
    } as unknown as RbacService;
    return { prisma, audit, rbac, service: new GovernanceService(prisma as any, audit, rbac) };
  };

  it('requires governance manage permission through the RBAC adapter', async () => {
    const { prisma, rbac, service } = makeService();
    (rbac.assertGlobalPermission as jest.Mock).mockRejectedValue(new Error('forbidden'));

    await expect(service.create({
      actorId: 'm-dev',
      permissions: ['project:view'],
      kind: 'requirement_type',
      name: '新功能',
    })).rejects.toThrow('forbidden');

    expect(prisma.governanceDictionary.create).not.toHaveBeenCalled();
  });

  it('blocks hard deletion of a task category that is used by dev tasks or test cases', async () => {
    const { prisma, rbac, service } = makeService();
    prisma.devTask.count.mockResolvedValue(1);
    prisma.testCase.count.mockResolvedValue(0);

    await expect(service.remove({
      actorId: 'm-admin',
      permissions: ['governance:manage'],
      kind: 'task_category',
      id: 'cat-1',
    })).rejects.toBeInstanceOf(BadRequestException);

    expect(rbac.assertGlobalPermission).toHaveBeenCalledWith({
      actorId: 'm-admin',
      permissions: ['governance:manage'],
      requiredPermissions: ['governance:manage'],
    });
    expect(prisma.taskCategory.delete).not.toHaveBeenCalled();
  });

  it('soft deletes requirement dictionaries and writes audit', async () => {
    const { prisma, audit, service } = makeService();
    prisma.governanceDictionary.update.mockResolvedValue({ id: 'dict-1', kind: 'requirement_type', deletedAt: new Date('2026-07-08T00:00:00.000Z') });

    await service.remove({ actorId: 'm-admin', permissions: ['governance:manage'], kind: 'requirement_type', id: 'dict-1' });

    expect(prisma.governanceDictionary.update).toHaveBeenCalledWith({
      where: { id: 'dict-1' },
      data: { deletedAt: expect.any(Date) },
    });
    expect((audit.record as jest.Mock)).toHaveBeenCalledWith(expect.objectContaining({
      actorId: 'm-admin',
      action: 'governance.dictionary_deleted',
      resourceType: 'governance_dictionary',
      resourceId: 'dict-1',
    }));
  });

  it('exports task categories and governance dictionaries together', async () => {
    const { prisma, service } = makeService();
    prisma.taskCategory.findMany.mockResolvedValue([{ id: 'cat-1', name: '前端' }]);
    prisma.governanceDictionary.findMany.mockResolvedValue([{ id: 'type-1', kind: 'requirement_type', name: '新功能' }]);

    await expect(service.exportAll()).resolves.toEqual({
      taskCategories: [{ id: 'cat-1', name: '前端' }],
      dictionaries: [{ id: 'type-1', kind: 'requirement_type', name: '新功能' }],
    });
  });
});
