/**
 * 日志管理服务测试
 * 
 * 功能描述：
 * - 测试日志管理服务的核心功能
 * - 验证定时任务的执行逻辑
 * - 测试日志统计和分析功能
 * - 验证日志文件操作的正确性
 * 
 * 职责分离：
 * - 功能测试：测试日志管理的各项核心功能
 * - 文件操作测试：验证日志文件的读写和清理
 * - 统计分析测试：测试日志统计数据的准确性
 * - 边界测试：验证各种边界条件和异常情况
 * 
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 新增日志管理服务测试文件
 * 
 * 测试覆盖：
 * - 服务实例化
 * - 日志目录路径获取
 * - 日志清理任务
 * - 日志统计功能
 * - 日志尾部读取
 * - 配置解析功能
 * 
 * @author moyin
 * @version 1.0.1
 * @since 2026-01-07
 * @lastModified 2026-01-07
 */

import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { LogManagementService } from './log_management.service';
import { AppLoggerService } from './logger.service';
import * as fs from 'fs';
import * as path from 'path';

// Mock fs module
jest.mock('fs');
jest.mock('path');

describe('LogManagementService', () => {
  let service: LogManagementService;
  let mockConfigService: jest.Mocked<ConfigService>;
  let mockLogger: jest.Mocked<AppLoggerService>;
  let mockFs: jest.Mocked<typeof fs>;
  let mockPath: jest.Mocked<typeof path>;

  beforeEach(async () => {
    // Reset mocks
    jest.clearAllMocks();

    // Setup mocks
    mockFs = fs as jest.Mocked<typeof fs>;
    mockPath = path as jest.Mocked<typeof path>;

    mockConfigService = {
      get: jest.fn((key: string, defaultValue?: any) => {
        const config: Record<string, any> = {
          LOG_DIR: './test-logs',
          LOG_MAX_FILES: '7d',
          LOG_MAX_SIZE: '10m',
          NODE_ENV: 'test',
        };
        return config[key] || defaultValue;
      }),
    } as any;

    mockLogger = {
      info: jest.fn(),
      warn: jest.fn(),
      error: jest.fn(),
      debug: jest.fn(),
      fatal: jest.fn(),
      trace: jest.fn(),
    } as any;

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        LogManagementService,
        {
          provide: ConfigService,
          useValue: mockConfigService,
        },
        {
          provide: AppLoggerService,
          useValue: mockLogger,
        },
      ],
    }).compile();

    service = module.get<LogManagementService>(LogManagementService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  /**
   * 测试获取日志目录绝对路径功能
   * 
   * 验证点：
   * - getLogDirAbsolutePath 方法能够正确返回绝对路径
   * - 调用了 path.resolve 方法
   * - 使用了正确的日志目录配置
   */
  describe('getLogDirAbsolutePath', () => {
    it('should return absolute path of log directory', () => {
      // Arrange
      const expectedPath = '/absolute/test-logs';
      mockPath.resolve.mockReturnValue(expectedPath);

      // Act
      const result = service.getLogDirAbsolutePath();

      // Assert
      expect(result).toBe(expectedPath);
      expect(mockPath.resolve).toHaveBeenCalledWith('./test-logs');
    });
  });

  /**
   * 测试日志清理任务功能
   * 
   * 验证点：
   * - cleanupOldLogs 方法能够正确执行清理逻辑
   * - 检查日志目录是否存在
   * - 正确处理文件扫描和删除
   * - 记录清理结果日志
   */
  describe('cleanupOldLogs', () => {
    it('should skip cleanup when log directory does not exist', async () => {
      // Arrange
      mockFs.existsSync.mockReturnValue(false);

      // Act
      await service.cleanupOldLogs();

      // Assert
      expect(mockFs.existsSync).toHaveBeenCalledWith('./test-logs');
      expect(mockLogger.warn).toHaveBeenCalledWith(
        '日志目录不存在，跳过清理任务',
        expect.objectContaining({
          operation: 'cleanupOldLogs',
          logDir: './test-logs',
        })
      );
    });

    it('should cleanup old log files successfully', async () => {
      // Arrange
      const oldDate = new Date();
      oldDate.setDate(oldDate.getDate() - 10); // 10 days old

      mockFs.existsSync.mockReturnValue(true);
      mockFs.readdirSync.mockReturnValue(['old.log', 'new.log'] as any);
      mockFs.statSync
        .mockReturnValueOnce({ 
          birthtime: oldDate, 
          size: 1024,
          extname: jest.fn().mockReturnValue('.log')
        } as any)
        .mockReturnValueOnce({ 
          birthtime: new Date(), 
          size: 2048,
          extname: jest.fn().mockReturnValue('.log')
        } as any);
      
      mockPath.join.mockImplementation((...args) => args.join('/'));
      mockPath.extname.mockReturnValue('.log');

      // Act
      await service.cleanupOldLogs();

      // Assert
      expect(mockFs.existsSync).toHaveBeenCalledWith('./test-logs');
      expect(mockFs.readdirSync).toHaveBeenCalledWith('./test-logs');
      expect(mockLogger.info).toHaveBeenCalledWith(
        '开始执行日志清理任务',
        expect.objectContaining({
          operation: 'cleanupOldLogs',
        })
      );
    });

    it('should handle cleanup errors gracefully', async () => {
      // Arrange
      const error = new Error('File system error');
      mockFs.existsSync.mockImplementation(() => {
        throw error;
      });

      // Act
      await service.cleanupOldLogs();

      // Assert
      expect(mockLogger.error).toHaveBeenCalledWith(
        '日志清理任务执行失败',
        expect.objectContaining({
          operation: 'cleanupOldLogs',
          error: 'File system error',
        }),
        error.stack
      );
    });
  });

  /**
   * 测试日志统计功能
   * 
   * 验证点：
   * - getLogStatistics 方法能够正确统计日志信息
   * - 处理日志目录不存在的情况
   * - 正确计算文件数量、大小等统计数据
   * - 处理统计过程中的异常情况
   */
  describe('getLogStatistics', () => {
    it('should return empty statistics when log directory does not exist', async () => {
      // Arrange
      mockFs.existsSync.mockReturnValue(false);

      // Act
      const result = await service.getLogStatistics();

      // Assert
      expect(result).toEqual({
        fileCount: 0,
        totalSize: 0,
        errorLogCount: 0,
        oldestFile: '',
        newestFile: '',
        avgFileSize: 0,
      });
    });

    it('should calculate statistics correctly', async () => {
      // Arrange
      const files = ['app.log', 'error.log', 'access.log'];
      const oldDate = new Date('2023-01-01');
      const newDate = new Date('2023-12-31');

      mockFs.existsSync.mockReturnValue(true);
      mockFs.readdirSync.mockReturnValue(files as any);
      mockFs.statSync
        .mockReturnValueOnce({ birthtime: oldDate, size: 1000 } as any)
        .mockReturnValueOnce({ birthtime: newDate, size: 2000 } as any)
        .mockReturnValueOnce({ birthtime: new Date('2023-06-01'), size: 1500 } as any);
      
      mockPath.join.mockImplementation((...args) => args.join('/'));

      // Act
      const result = await service.getLogStatistics();

      // Assert
      expect(result).toEqual({
        fileCount: 3,
        totalSize: 4500,
        errorLogCount: 1, // error.log
        oldestFile: 'app.log',
        newestFile: 'error.log',
        avgFileSize: 1500,
      });
    });

    it('should handle statistics errors and rethrow', async () => {
      // Arrange
      const error = new Error('Statistics error');
      mockFs.existsSync.mockImplementation(() => {
        throw error;
      });

      // Act & Assert
      await expect(service.getLogStatistics()).rejects.toThrow('Statistics error');
      expect(mockLogger.error).toHaveBeenCalledWith(
        '获取日志统计信息失败',
        expect.objectContaining({
          operation: 'getLogStatistics',
          error: 'Statistics error',
        }),
        error.stack
      );
    });
  });

  /**
   * 测试日志尾部读取功能
   * 
   * 验证点：
   * - getRuntimeLogTail 方法能够正确读取日志尾部
   * - 处理文件不存在的情况
   * - 正确解析不同环境的日志文件类型
   * - 限制读取行数在合理范围内
   */
  describe('getRuntimeLogTail', () => {
    it('should return empty lines when file does not exist', async () => {
      // Arrange
      mockFs.existsSync.mockReturnValue(false);
      mockPath.join.mockImplementation((...args) => args.join('/'));

      // Act
      const result = await service.getRuntimeLogTail();

      // Assert
      expect(result).toEqual({
        file: 'dev.log',
        updated_at: expect.any(String),
        lines: [],
      });
    });

    it('should read log tail successfully', async () => {
      // Arrange
      const mockStats = {
        size: 1000,
        mtime: new Date('2023-12-31T12:00:00Z'),
      };
      const mockFd = 123;

      mockFs.existsSync.mockReturnValue(true);
      mockFs.statSync.mockReturnValue(mockStats as any);
      mockFs.openSync.mockReturnValue(mockFd);
      mockFs.readSync.mockImplementation(() => 0);
      mockFs.closeSync.mockImplementation();
      mockPath.join.mockImplementation((...args) => args.join('/'));

      // Act
      const result = await service.getRuntimeLogTail({ lines: 10 });

      // Assert - Verify the method executes and returns expected structure
      expect(result).toHaveProperty('file');
      expect(result).toHaveProperty('updated_at');
      expect(result).toHaveProperty('lines');
      expect(result.file).toBe('dev.log');
      expect(result.updated_at).toBe('2023-12-31T12:00:00.000Z');
      expect(Array.isArray(result.lines)).toBe(true);
      expect(mockFs.closeSync).toHaveBeenCalledWith(mockFd);
    });

    it('should limit requested lines to maximum allowed', async () => {
      // Arrange
      mockFs.existsSync.mockReturnValue(false);
      mockPath.join.mockImplementation((...args) => args.join('/'));

      // Act
      const result = await service.getRuntimeLogTail({ lines: 5000 }); // Over limit

      // Assert - Should be limited to 2000 lines max
      expect(result.lines).toEqual([]);
    });
  });

  /**
   * 测试配置解析功能
   * 
   * 验证点：
   * - parseMaxFiles 私有方法能够正确解析时间配置
   * - parseSize 私有方法能够正确解析大小配置
   * - formatBytes 私有方法能够正确格式化字节数
   */
  describe('private methods', () => {
    it('should parse max files configuration correctly', () => {
      // Access private method for testing
      const parseMaxFiles = (service as any).parseMaxFiles;

      expect(parseMaxFiles('7d')).toBe(7);
      expect(parseMaxFiles('2w')).toBe(14);
      expect(parseMaxFiles('1m')).toBe(30);
      expect(parseMaxFiles('30')).toBe(30);
    });

    it('should parse size configuration correctly', () => {
      // Access private method for testing
      const parseSize = (service as any).parseSize;

      expect(parseSize('10k')).toBe(10 * 1024);
      expect(parseSize('5m')).toBe(5 * 1024 * 1024);
      expect(parseSize('1g')).toBe(1 * 1024 * 1024 * 1024);
    });

    it('should format bytes correctly', () => {
      // Access private method for testing
      const formatBytes = (service as any).formatBytes;

      expect(formatBytes(0)).toBe('0 B');
      expect(formatBytes(1024)).toBe('1 KB');
      expect(formatBytes(1024 * 1024)).toBe('1 MB');
      expect(formatBytes(1024 * 1024 * 1024)).toBe('1 GB');
      expect(formatBytes(1536)).toBe('1.5 KB');
    });
  });
});