/**
 * AdminDatabaseExceptionFilter 单元测试
 * 
 * 功能描述：
 * - 测试管理员数据库异常过滤器的所有功能
 * - 验证异常处理和错误响应格式化的正确性
 * - 测试各种异常类型的处理
 * 
 * 职责分离：
 * - 异常过滤器逻辑测试，不涉及具体业务
 * - Mock HTTP上下文，专注过滤器功能
 * - 验证错误响应的格式和内容
 * 
 * 最近修改：
 * - 2026-01-09: 功能新增 - 创建AdminDatabaseExceptionFilter单元测试 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-09
 * @lastModified 2026-01-09
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ArgumentsHost, HttpStatus, HttpException } from '@nestjs/common';
import {
  BadRequestException,
  UnauthorizedException,
  ForbiddenException,
  NotFoundException,
  ConflictException,
  UnprocessableEntityException,
  InternalServerErrorException,
} from '@nestjs/common';
import { AdminDatabaseExceptionFilter } from './admin_database_exception.filter';

describe('AdminDatabaseExceptionFilter', () => {
  let filter: AdminDatabaseExceptionFilter;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [AdminDatabaseExceptionFilter],
    }).compile();

    filter = module.get<AdminDatabaseExceptionFilter>(AdminDatabaseExceptionFilter);
  });

  const createMockArgumentsHost = (requestData: any = {}) => {
    const mockRequest = {
      method: 'POST',
      url: '/admin/database/users',
      ip: '127.0.0.1',
      get: jest.fn().mockReturnValue('test-user-agent'),
      body: { username: 'testuser' },
      query: { limit: '10' },
      ...requestData,
    };

    const mockResponse = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };

    const mockHost = {
      switchToHttp: () => ({
        getRequest: () => mockRequest,
        getResponse: () => mockResponse,
      }),
    } as ArgumentsHost;

    return { mockHost, mockRequest, mockResponse };
  };

  describe('catch', () => {
    it('should handle BadRequestException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new BadRequestException('Invalid input data');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Invalid input data',
          error_code: 'BAD_REQUEST',
          path: '/admin/database/users',
          method: 'POST',
          timestamp: expect.any(String),
          request_id: expect.any(String),
        })
      );
    });

    it('should handle UnauthorizedException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new UnauthorizedException('Access denied');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.UNAUTHORIZED);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Access denied',
          error_code: 'UNAUTHORIZED',
        })
      );
    });

    it('should handle ForbiddenException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new ForbiddenException('Insufficient permissions');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.FORBIDDEN);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Insufficient permissions',
          error_code: 'FORBIDDEN',
        })
      );
    });

    it('should handle NotFoundException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new NotFoundException('User not found');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.NOT_FOUND);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'User not found',
          error_code: 'NOT_FOUND',
        })
      );
    });

    it('should handle ConflictException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new ConflictException('Username already exists');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.CONFLICT);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Username already exists',
          error_code: 'CONFLICT',
        })
      );
    });

    it('should handle UnprocessableEntityException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new UnprocessableEntityException('Validation failed');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.UNPROCESSABLE_ENTITY);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Validation failed',
          error_code: 'UNPROCESSABLE_ENTITY',
        })
      );
    });

    it('should handle InternalServerErrorException', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new InternalServerErrorException('Database connection failed');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Database connection failed',
          error_code: 'INTERNAL_SERVER_ERROR',
        })
      );
    });

    it('should handle unknown exceptions', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new Error('Unknown error');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.INTERNAL_SERVER_ERROR);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: '系统内部错误，请稍后重试',
          error_code: 'INTERNAL_SERVER_ERROR',
        })
      );
    });

    it('should handle exception with object response', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new BadRequestException({
        message: 'Validation error',
        details: [
          { field: 'username', constraint: 'minLength', received_value: 'ab' }
        ]
      });

      filter.catch(exception, mockHost);

      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Validation error',
          error_code: 'BAD_REQUEST',
          details: [
            { field: 'username', constraint: 'minLength', received_value: 'ab' }
          ],
        })
      );
    });

    it('should handle exception with nested error message', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new BadRequestException({
        error: 'Custom error message'
      });

      filter.catch(exception, mockHost);

      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          message: 'Custom error message',
        })
      );
    });

    it('should sanitize sensitive fields in request body', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost({
        body: {
          username: 'testuser',
          password: 'secret123',
          api_key: 'sensitive-key'
        }
      });
      const exception = new BadRequestException('Invalid data');

      filter.catch(exception, mockHost);

      // 验证响应被正确处理（敏感字段在日志中被清理，但不影响响应）
      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Invalid data',
          error_code: 'BAD_REQUEST',
        })
      );
    });

    it('should handle missing user agent', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      mockHost.switchToHttp().getRequest().get = jest.fn().mockReturnValue(undefined);
      
      const exception = new BadRequestException('Test error');

      filter.catch(exception, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.BAD_REQUEST);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          success: false,
          message: 'Test error',
        })
      );
    });

    it('should handle exception with string response', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new BadRequestException('Simple string error');

      filter.catch(exception, mockHost);

      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          message: 'Simple string error',
        })
      );
    });

    it('should generate unique request IDs', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception1 = new BadRequestException('Error 1');
      const exception2 = new BadRequestException('Error 2');

      filter.catch(exception1, mockHost);
      const firstCall = mockResponse.json.mock.calls[0][0];

      mockResponse.json.mockClear();
      filter.catch(exception2, mockHost);
      const secondCall = mockResponse.json.mock.calls[0][0];

      expect(firstCall.request_id).toBeDefined();
      expect(secondCall.request_id).toBeDefined();
      expect(firstCall.request_id).not.toBe(secondCall.request_id);
    });

    it('should include timestamp in response', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      const exception = new BadRequestException('Test error');

      const beforeTime = new Date().toISOString();
      filter.catch(exception, mockHost);
      const afterTime = new Date().toISOString();

      const response = mockResponse.json.mock.calls[0][0];
      expect(response.timestamp).toBeDefined();
      expect(response.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
      expect(response.timestamp >= beforeTime).toBe(true);
      expect(response.timestamp <= afterTime).toBe(true);
    });

    it('should handle different HTTP status codes', () => {
      const { mockHost, mockResponse } = createMockArgumentsHost();
      
      // 创建一个继承自HttpException的异常，模拟429状态码
      class TooManyRequestsException extends HttpException {
        constructor(message: string) {
          super(message, HttpStatus.TOO_MANY_REQUESTS);
        }
      }

      const tooManyRequestsException = new TooManyRequestsException('Too many requests');

      filter.catch(tooManyRequestsException, mockHost);

      expect(mockResponse.status).toHaveBeenCalledWith(HttpStatus.TOO_MANY_REQUESTS);
      expect(mockResponse.json).toHaveBeenCalledWith(
        expect.objectContaining({
          error_code: 'TOO_MANY_REQUESTS',
        })
      );
    });
  });
});