/**
 * AdminOperationLogInterceptor 单元测试
 * 
 * 功能描述：
 * - 测试管理员操作日志拦截器的所有功能
 * - 验证操作拦截和日志记录的正确性
 * - 测试成功和失败场景的处理
 * 
 * 职责分离：
 * - 拦截器逻辑测试，不涉及具体业务
 * - Mock日志服务，专注拦截器功能
 * - 验证日志记录的完整性和准确性
 * 
 * 最近修改：
 * - 2026-01-09: 功能新增 - 创建AdminOperationLogInterceptor单元测试 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-09
 * @lastModified 2026-01-09
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ExecutionContext, CallHandler } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { of, throwError } from 'rxjs';
import { AdminOperationLogInterceptor } from './admin_operation_log.interceptor';
import { AdminOperationLogService } from './admin_operation_log.service';
import { LOG_ADMIN_OPERATION_KEY, LogAdminOperationOptions } from './log_admin_operation.decorator';
import { OPERATION_RESULTS } from './admin_constants';

describe('AdminOperationLogInterceptor', () => {
  let interceptor: AdminOperationLogInterceptor;
  let logService: jest.Mocked<AdminOperationLogService>;
  let reflector: jest.Mocked<Reflector>;

  const mockLogService = {
    createLog: jest.fn(),
  };

  const mockReflector = {
    get: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        AdminOperationLogInterceptor,
        {
          provide: AdminOperationLogService,
          useValue: mockLogService,
        },
        {
          provide: Reflector,
          useValue: mockReflector,
        },
      ],
    }).compile();

    interceptor = module.get<AdminOperationLogInterceptor>(AdminOperationLogInterceptor);
    logService = module.get(AdminOperationLogService);
    reflector = module.get(Reflector);
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  const createMockExecutionContext = (requestData: any = {}) => {
    const mockRequest = {
      method: 'POST',
      url: '/admin/users',
      route: { path: '/admin/users' },
      params: { id: '1' },
      query: { limit: '10' },
      body: { username: 'testuser' },
      headers: { 'user-agent': 'test-agent' },
      user: { id: 'admin1', username: 'admin' },
      ip: '127.0.0.1',
      ...requestData,
    };

    const mockResponse = {};

    const mockContext = {
      switchToHttp: () => ({
        getRequest: () => mockRequest,
        getResponse: () => mockResponse,
      }),
      getHandler: () => ({}),
    } as ExecutionContext;

    return { mockContext, mockRequest, mockResponse };
  };

  const createMockCallHandler = (responseData: any = { success: true }) => {
    return {
      handle: () => of(responseData),
    } as CallHandler;
  };

  describe('intercept', () => {
    it('should pass through when no log options configured', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler();

      reflector.get.mockReturnValue(undefined);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: (result) => {
          expect(result).toEqual({ success: true });
          expect(logService.createLog).not.toHaveBeenCalled();
          done();
        },
      });
    });

    it('should log successful operation', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler({ success: true, data: { id: '1' } });

      const logOptions: LogAdminOperationOptions = {
        operationType: 'CREATE',
        targetType: 'users',
        description: 'Create user',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: (result) => {
          expect(result).toEqual({ success: true, data: { id: '1' } });
          
          // 验证日志记录调用
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              adminUserId: 'admin1',
              adminUsername: 'admin',
              operationType: 'CREATE',
              targetType: 'users',
              operationDescription: 'Create user',
              httpMethodPath: 'POST /admin/users',
              operationResult: OPERATION_RESULTS.SUCCESS,
              targetId: '1',
              requestParams: expect.objectContaining({
                params: { id: '1' },
                query: { limit: '10' },
                body: { username: 'testuser' },
              }),
              afterData: { success: true, data: { id: '1' } },
              clientIp: '127.0.0.1',
              userAgent: 'test-agent',
            })
          );
          done();
        },
      });
    });

    it('should log failed operation', (done) => {
      const { mockContext } = createMockExecutionContext();
      const error = new Error('Operation failed');
      const mockHandler = {
        handle: () => throwError(() => error),
      } as CallHandler;

      const logOptions: LogAdminOperationOptions = {
        operationType: 'UPDATE',
        targetType: 'users',
        description: 'Update user',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        error: (err) => {
          expect(err).toBe(error);
          
          // 验证错误日志记录调用
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              adminUserId: 'admin1',
              adminUsername: 'admin',
              operationType: 'UPDATE',
              targetType: 'users',
              operationDescription: 'Update user',
              operationResult: OPERATION_RESULTS.FAILED,
              errorMessage: 'Operation failed',
              errorCode: 'UNKNOWN_ERROR',
            })
          );
          done();
        },
      });
    });

    it('should handle missing admin user', (done) => {
      const { mockContext } = createMockExecutionContext({ user: undefined });
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'QUERY',
        targetType: 'users',
        description: 'Query users',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              adminUserId: 'unknown',
              adminUsername: 'unknown',
            })
          );
          done();
        },
      });
    });

    it('should handle sensitive operations', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'DELETE',
        targetType: 'users',
        description: 'Delete user',
        isSensitive: true,
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              isSensitive: true,
            })
          );
          done();
        },
      });
    });

    it('should disable request params capture when configured', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'QUERY',
        targetType: 'users',
        description: 'Query users',
        captureRequestParams: false,
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              requestParams: undefined,
            })
          );
          done();
        },
      });
    });

    it('should disable after data capture when configured', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler({ data: 'sensitive' });

      const logOptions: LogAdminOperationOptions = {
        operationType: 'QUERY',
        targetType: 'users',
        description: 'Query users',
        captureAfterData: false,
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              afterData: undefined,
            })
          );
          done();
        },
      });
    });

    it('should extract affected records from response', (done) => {
      const { mockContext } = createMockExecutionContext();
      const responseData = {
        success: true,
        data: {
          items: [{ id: '1' }, { id: '2' }, { id: '3' }],
          total: 3,
        },
      };
      const mockHandler = createMockCallHandler(responseData);

      const logOptions: LogAdminOperationOptions = {
        operationType: 'QUERY',
        targetType: 'users',
        description: 'Query users',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              affectedRecords: 3, // Should extract from items array length
            })
          );
          done();
        },
      });
    });

    it('should handle log service errors gracefully', (done) => {
      const { mockContext } = createMockExecutionContext();
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'CREATE',
        targetType: 'users',
        description: 'Create user',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockRejectedValue(new Error('Log service error'));

      // 即使日志记录失败，原始操作也应该成功
      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: (result) => {
          expect(result).toEqual({ success: true });
          expect(logService.createLog).toHaveBeenCalled();
          done();
        },
      });
    });

    it('should extract target ID from different sources', (done) => {
      const { mockContext } = createMockExecutionContext({
        params: {},
        body: { id: 'body-id' },
        query: { id: 'query-id' },
      });
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'UPDATE',
        targetType: 'users',
        description: 'Update user',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              targetId: 'body-id', // Should prefer body over query
            })
          );
          done();
        },
      });
    });

    it('should handle missing route information', (done) => {
      const { mockContext } = createMockExecutionContext({
        route: undefined,
        url: '/admin/custom-endpoint',
      });
      const mockHandler = createMockCallHandler();

      const logOptions: LogAdminOperationOptions = {
        operationType: 'QUERY',
        targetType: 'custom',
        description: 'Custom operation',
      };

      reflector.get.mockReturnValue(logOptions);
      logService.createLog.mockResolvedValue({} as any);

      interceptor.intercept(mockContext, mockHandler).subscribe({
        next: () => {
          expect(logService.createLog).toHaveBeenCalledWith(
            expect.objectContaining({
              httpMethodPath: 'POST /admin/custom-endpoint',
            })
          );
          done();
        },
      });
    });
  });
});