import { CallHandler, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { lastValueFrom, of, throwError } from 'rxjs';
import { AuditService } from './audit.service';
import { OperationLogInterceptor } from './operation-log.interceptor';

function createContext(request: Record<string, unknown>) {
  return {
    getHandler: () => function handler() {},
    getClass: () => class Controller {},
    switchToHttp: () => ({
      getRequest: () => request,
    }),
  } as unknown as ExecutionContext;
}

describe('OperationLogInterceptor', () => {
  it('writes a success operation log with masked request body', async () => {
    const write = jest.fn();
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue({
        action: 'admin.users.create',
        resourceType: 'user',
        resourceIdFromResult: 'id',
        metadataFromBody: ['name', 'password'],
      }),
    } as unknown as Reflector;
    const interceptor = new OperationLogInterceptor(
      { write } as unknown as AuditService,
      reflector,
    );

    const request = {
      user: { id: 'actor-1', name: 'root' },
      params: {},
      query: {},
      body: { name: 'demo', password: '123456' },
      headers: { 'user-agent': 'vitest' },
      ip: '127.0.0.1',
    };
    const next = {
      handle: () => of({ id: 'user-1' }),
    } as CallHandler;

    await expect(lastValueFrom(interceptor.intercept(createContext(request), next))).resolves.toEqual({
      id: 'user-1',
    });

    expect(write).toHaveBeenCalledWith(
      expect.objectContaining({
        actorId: 'actor-1',
        actorUsername: 'root',
        action: 'admin.users.create',
        resourceType: 'user',
        resourceId: 'user-1',
        ipAddress: '127.0.0.1',
        metadata: expect.objectContaining({
          status: 'success',
          request: expect.objectContaining({
            body: { name: 'demo', password: '******' },
          }),
        }),
      }),
    );
  });

  it('writes a failed operation log and rethrows the error', async () => {
    const write = jest.fn();
    const reflector = {
      getAllAndOverride: jest.fn().mockReturnValue({
        action: 'admin.users.update',
        resourceType: 'user',
        resourceIdParam: 'id',
      }),
    } as unknown as Reflector;
    const interceptor = new OperationLogInterceptor(
      { write } as unknown as AuditService,
      reflector,
    );
    const error = new Error('保存失败');
    const request = {
      user: { id: 'actor-1', name: 'root' },
      params: { id: 'user-1' },
      query: {},
      body: {},
      headers: {},
      ip: '127.0.0.1',
    };
    const next = {
      handle: () => throwError(() => error),
    } as CallHandler;

    await expect(
      lastValueFrom(interceptor.intercept(createContext(request), next)),
    ).rejects.toThrow('保存失败');

    expect(write).toHaveBeenCalledWith(
      expect.objectContaining({
        action: 'admin.users.update',
        resourceId: 'user-1',
        metadata: expect.objectContaining({
          status: 'failed',
          error: {
            name: 'Error',
            message: '保存失败',
          },
        }),
      }),
    );
  });
});
