import 'reflect-metadata';
import { ValidationPipe } from '@nestjs/common';
import { PIPES_METADATA } from '@nestjs/common/constants';
import { PERMISSION_KEY } from '../auth/decorators/permission.decorator';
import { BatchIdsDto } from '../common/batch-ids.dto';
import { ExamsController } from './exams.controller';

describe('ExamsController batch archive and restore', () => {
  const req = {
    user: {
      id: 7,
      username: 'admin',
      isSuperAdmin: true,
      permissions: ['exam:view'],
    },
    ip: '127.0.0.1',
    headers: {},
  };

  it.each(['batchArchive', 'batchRestore'] as const)(
    '%s uses the existing exam permission',
    (method) => {
      const handler = ExamsController.prototype[method] as (...args: never[]) => unknown;
      expect(Reflect.getMetadata(PERMISSION_KEY, handler)).toEqual(['exam:view']);
    },
  );

  it('class-level validation rejects invalid and non-whitelisted batch bodies', async () => {
    const pipes = Reflect.getMetadata(PIPES_METADATA, ExamsController) as ValidationPipe[];
    expect(pipes).toHaveLength(1);
    const metadata = { type: 'body' as const, metatype: BatchIdsDto, data: undefined };
    await expect(pipes[0].transform({ ids: [] }, metadata)).rejects.toBeDefined();
    await expect(pipes[0].transform({ ids: [0] }, metadata)).rejects.toBeDefined();
    await expect(
      pipes[0].transform({ ids: [1], unexpected: true }, metadata),
    ).rejects.toBeDefined();
  });

  it('passes ids and access context to services and writes batch audit logs', async () => {
    const service = {
      batchArchive: jest.fn().mockResolvedValue({ archived: 1, skipped: 1 }),
      batchRestore: jest.fn().mockResolvedValue({ restored: 1, skipped: 1 }),
    };
    const log = jest.fn().mockResolvedValue(undefined);
    const controller = new ExamsController(service as never, { log } as never);

    await expect(controller.batchArchive({ ids: [1, 2] }, req)).resolves.toEqual({
      archived: 1,
      skipped: 1,
    });
    await expect(controller.batchRestore({ ids: [3, 4] }, req)).resolves.toEqual({
      restored: 1,
      skipped: 1,
    });

    expect(service.batchArchive).toHaveBeenCalledWith([1, 2], 7, true);
    expect(service.batchRestore).toHaveBeenCalledWith([3, 4], 7, true);
    expect(log.mock.calls.map(([entry]) => [entry.action, entry.detail])).toEqual([
      ['批量归档考试', 'IDs: 1,2'],
      ['批量恢复考试', 'IDs: 3,4'],
    ]);
  });

  it('requires exam:purge and writes permanent delete logs', async () => {
    expect(Reflect.getMetadata(PERMISSION_KEY, ExamsController.prototype.purge)).toEqual([
      'exam:purge',
    ]);
    expect(
      Reflect.getMetadata(PERMISSION_KEY, ExamsController.prototype.batchPurge),
    ).toEqual(['exam:purge']);

    const service = {
      purge: jest.fn().mockResolvedValue({ message: '已永久删除考试（不可恢复）' }),
    };
    const log = jest.fn().mockResolvedValue(undefined);
    const controller = new ExamsController(service as never, { log } as never);
    await controller.purge(1, req);
    expect(service.purge).toHaveBeenCalledWith(1, 7, true);
    expect(log).toHaveBeenCalledWith(
      expect.objectContaining({ module: '考试管理', action: '永久删除考试', targetId: 1 }),
    );
  });
});
