/**
 * 动态配置控制器测试
 * 
 * 功能描述：
 * - 测试动态配置管理的REST API控制器
 * - 验证配置获取、同步和管理功能
 * - 测试配置状态查询和备份管理
 * - 测试错误处理和异常情况
 * - 确保配置管理API的正确性和健壮性
 * 
 * 最近修改：
 * - 2026-01-12: 代码规范优化 - 创建缺失的测试文件，确保测试覆盖完整性 (修改者: moyin)
 * 
 * @author moyin
 * @version 1.0.0
 * @since 2026-01-12
 * @lastModified 2026-01-12
 */

import { Test, TestingModule } from '@nestjs/testing';
import { HttpException, HttpStatus } from '@nestjs/common';
import { DynamicConfigController } from './dynamic_config.controller';
import { DynamicConfigManagerService } from '../../core/zulip_core/services/dynamic_config_manager.service';

describe('DynamicConfigController', () => {
  let controller: DynamicConfigController;
  let configManagerService: jest.Mocked<DynamicConfigManagerService>;

  const mockConfig = {
    version: '2.0.0',
    lastModified: '2026-01-12T00:00:00.000Z',
    description: '测试配置',
    source: 'remote',
    maps: [
      {
        mapId: 'whale_port',
        mapName: '鲸之港',
        zulipStream: 'Whale Port',
        zulipStreamId: 5,
        description: '中心城区',
        isPublic: true,
        isWebPublic: false,
        interactionObjects: [
          {
            objectId: 'whale_port_general',
            objectName: 'General讨论区',
            zulipTopic: 'General',
            position: { x: 100, y: 100 },
            lastMessageId: 0
          }
        ]
      }
    ]
  };

  const mockSyncResult = {
    success: true,
    source: 'remote' as const,
    mapCount: 1,
    objectCount: 1,
    lastUpdated: new Date(),
    backupCreated: true
  };

  const mockConfigStatus = {
    hasRemoteCredentials: true,
    lastSyncTime: new Date(),
    hasLocalConfig: true,
    configSource: 'remote',
    configVersion: '2.0.0',
    mapCount: 1,
    objectCount: 1,
    syncIntervalMinutes: 30,
    configFile: '/path/to/config.json',
    backupDir: '/path/to/backups'
  };

  const mockBackupFiles = [
    {
      name: 'map-config-backup-2026-01-12T10-00-00-000Z.json',
      path: '/path/to/backup.json',
      size: 1024,
      created: new Date('2026-01-12T10:00:00.000Z')
    }
  ];

  beforeEach(async () => {
    const mockConfigManager = {
      getConfig: jest.fn(),
      syncConfig: jest.fn(),
      getConfigStatus: jest.fn(),
      getBackupFiles: jest.fn(),
      restoreFromBackup: jest.fn(),
      testZulipConnection: jest.fn(),
      getZulipStreams: jest.fn(),
      getZulipTopics: jest.fn(),
      getStreamByMap: jest.fn(),
      getMapIdByStream: jest.fn(),
      getAllMapConfigs: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [DynamicConfigController],
      providers: [
        {
          provide: DynamicConfigManagerService,
          useValue: mockConfigManager,
        },
      ],
    }).compile();

    controller = module.get<DynamicConfigController>(DynamicConfigController);
    configManagerService = module.get(DynamicConfigManagerService);
  });

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

  describe('getCurrentConfig', () => {
    it('should return current configuration', async () => {
      configManagerService.getConfig.mockResolvedValue(mockConfig);
      configManagerService.getConfigStatus.mockReturnValue(mockConfigStatus);

      const result = await controller.getCurrentConfig();

      expect(result.success).toBe(true);
      expect(result.data).toEqual(mockConfig);
      expect(result.source).toBe(mockConfig.source);
      expect(configManagerService.getConfig).toHaveBeenCalled();
    });

    it('should handle errors gracefully', async () => {
      configManagerService.getConfig.mockRejectedValue(new Error('Config error'));

      await expect(controller.getCurrentConfig()).rejects.toThrow(HttpException);
    });
  });

  describe('syncConfig', () => {
    it('should sync configuration successfully', async () => {
      configManagerService.syncConfig.mockResolvedValue(mockSyncResult);

      const result = await controller.syncConfig();

      expect(result.success).toBe(true);
      expect(result.data.source).toBe(mockSyncResult.source);
      expect(result.data.mapCount).toBe(mockSyncResult.mapCount);
      expect(configManagerService.syncConfig).toHaveBeenCalled();
    });

    it('should handle sync failures', async () => {
      const failedSyncResult = {
        ...mockSyncResult,
        success: false,
        error: 'Sync failed'
      };
      configManagerService.syncConfig.mockResolvedValue(failedSyncResult);

      const result = await controller.syncConfig();

      expect(result.success).toBe(false);
      expect(result.error).toBe('Sync failed');
    });

    it('should handle sync errors', async () => {
      configManagerService.syncConfig.mockRejectedValue(new Error('Network error'));

      await expect(controller.syncConfig()).rejects.toThrow(HttpException);
    });
  });

  describe('getConfigStatus', () => {
    it('should return configuration status', async () => {
      configManagerService.getConfigStatus.mockReturnValue(mockConfigStatus);

      const result = await controller.getConfigStatus();

      expect(result.success).toBe(true);
      expect(result.data).toMatchObject(mockConfigStatus);
      expect(configManagerService.getConfigStatus).toHaveBeenCalled();
    });

    it('should handle status retrieval errors', async () => {
      configManagerService.getConfigStatus.mockImplementation(() => {
        throw new Error('Status error');
      });

      await expect(controller.getConfigStatus()).rejects.toThrow(HttpException);
    });
  });

  describe('getBackups', () => {
    it('should return list of backup files', async () => {
      configManagerService.getBackupFiles.mockReturnValue(mockBackupFiles);

      const result = await controller.getBackups();

      expect(result.success).toBe(true);
      expect(result.data.backups).toHaveLength(1);
      expect(result.data.count).toBe(1);
      expect(configManagerService.getBackupFiles).toHaveBeenCalled();
    });

    it('should return empty array when no backups exist', async () => {
      configManagerService.getBackupFiles.mockReturnValue([]);

      const result = await controller.getBackups();

      expect(result.success).toBe(true);
      expect(result.data.backups).toEqual([]);
      expect(result.data.count).toBe(0);
    });

    it('should handle backup listing errors', async () => {
      configManagerService.getBackupFiles.mockImplementation(() => {
        throw new Error('Backup error');
      });

      await expect(controller.getBackups()).rejects.toThrow(HttpException);
    });
  });

  describe('restoreFromBackup', () => {
    const backupFileName = 'map-config-backup-2026-01-12T10-00-00-000Z.json';

    it('should restore from backup successfully', async () => {
      configManagerService.restoreFromBackup.mockResolvedValue(true);

      const result = await controller.restoreFromBackup(backupFileName);

      expect(result.success).toBe(true);
      expect(result.data.backupFile).toBe(backupFileName);
      expect(result.data.message).toBe('配置恢复成功');
      expect(configManagerService.restoreFromBackup).toHaveBeenCalledWith(backupFileName);
    });

    it('should handle restore failure', async () => {
      configManagerService.restoreFromBackup.mockResolvedValue(false);

      const result = await controller.restoreFromBackup(backupFileName);

      expect(result.success).toBe(false);
      expect(result.data.message).toBe('配置恢复失败');
    });

    it('should handle restore errors', async () => {
      configManagerService.restoreFromBackup.mockRejectedValue(new Error('Restore error'));

      await expect(controller.restoreFromBackup(backupFileName)).rejects.toThrow(HttpException);
    });
  });

  describe('testConnection', () => {
    it('should test Zulip connection successfully', async () => {
      configManagerService.testZulipConnection.mockResolvedValue(true);

      const result = await controller.testConnection();

      expect(result.success).toBe(true);
      expect(result.data.connected).toBe(true);
      expect(result.data.message).toBe('Zulip连接正常');
      expect(configManagerService.testZulipConnection).toHaveBeenCalled();
    });

    it('should handle connection failure', async () => {
      configManagerService.testZulipConnection.mockResolvedValue(false);

      const result = await controller.testConnection();

      expect(result.success).toBe(true);
      expect(result.data.connected).toBe(false);
      expect(result.data.message).toBe('Zulip连接失败');
    });

    it('should handle connection test errors', async () => {
      configManagerService.testZulipConnection.mockRejectedValue(new Error('Connection error'));

      const result = await controller.testConnection();

      expect(result.success).toBe(false);
      expect(result.data.connected).toBe(false);
      expect(result.error).toBe('Connection error');
    });
  });

  describe('getStreams', () => {
    const mockStreams = [
      {
        stream_id: 5,
        name: 'Whale Port',
        description: 'Main port area',
        invite_only: false,
        is_web_public: false,
        stream_post_policy: 1,
        message_retention_days: null,
        history_public_to_subscribers: true,
        first_message_id: null,
        is_announcement_only: false
      }
    ];

    it('should return Zulip streams', async () => {
      configManagerService.getZulipStreams.mockResolvedValue(mockStreams);

      const result = await controller.getStreams();

      expect(result.success).toBe(true);
      expect(result.data.streams).toHaveLength(1);
      expect(result.data.count).toBe(1);
      expect(configManagerService.getZulipStreams).toHaveBeenCalled();
    });

    it('should handle stream retrieval errors', async () => {
      configManagerService.getZulipStreams.mockRejectedValue(new Error('Stream error'));

      await expect(controller.getStreams()).rejects.toThrow(HttpException);
    });
  });

  describe('getTopics', () => {
    const streamId = 5;
    const mockTopics = [
      { name: 'General', max_id: 123 },
      { name: 'Random', max_id: 456 }
    ];

    it('should return topics for a stream', async () => {
      configManagerService.getZulipTopics.mockResolvedValue(mockTopics);

      const result = await controller.getTopics(streamId.toString());

      expect(result.success).toBe(true);
      expect(result.data.streamId).toBe(streamId);
      expect(result.data.topics).toHaveLength(2);
      expect(result.data.count).toBe(2);
      expect(configManagerService.getZulipTopics).toHaveBeenCalledWith(streamId);
    });

    it('should handle invalid stream ID', async () => {
      await expect(controller.getTopics('invalid')).rejects.toThrow(HttpException);
    });

    it('should handle topic retrieval errors', async () => {
      configManagerService.getZulipTopics.mockRejectedValue(new Error('Topic error'));

      await expect(controller.getTopics(streamId.toString())).rejects.toThrow(HttpException);
    });
  });

  describe('mapToStream', () => {
    const mapId = 'whale_port';

    it('should return stream name for map ID', async () => {
      configManagerService.getStreamByMap.mockResolvedValue('Whale Port');

      const result = await controller.mapToStream(mapId);

      expect(result.success).toBe(true);
      expect(result.data.mapId).toBe(mapId);
      expect(result.data.streamName).toBe('Whale Port');
      expect(result.data.found).toBe(true);
      expect(configManagerService.getStreamByMap).toHaveBeenCalledWith(mapId);
    });

    it('should handle map not found', async () => {
      configManagerService.getStreamByMap.mockResolvedValue(null);

      const result = await controller.mapToStream('invalid_map');

      expect(result.success).toBe(true);
      expect(result.data.mapId).toBe('invalid_map');
      expect(result.data.streamName).toBe(null);
      expect(result.data.found).toBe(false);
    });

    it('should handle map stream retrieval errors', async () => {
      configManagerService.getStreamByMap.mockRejectedValue(new Error('Map error'));

      await expect(controller.mapToStream(mapId)).rejects.toThrow(HttpException);
    });
  });

  describe('streamToMap', () => {
    const streamName = 'Whale Port';

    it('should return map ID for stream name', async () => {
      configManagerService.getMapIdByStream.mockResolvedValue('whale_port');

      const result = await controller.streamToMap(streamName);

      expect(result.success).toBe(true);
      expect(result.data.streamName).toBe(streamName);
      expect(result.data.mapId).toBe('whale_port');
      expect(result.data.found).toBe(true);
      expect(configManagerService.getMapIdByStream).toHaveBeenCalledWith(streamName);
    });

    it('should handle stream not found', async () => {
      configManagerService.getMapIdByStream.mockResolvedValue(null);

      const result = await controller.streamToMap('Invalid Stream');

      expect(result.success).toBe(true);
      expect(result.data.streamName).toBe('Invalid Stream');
      expect(result.data.mapId).toBe(null);
      expect(result.data.found).toBe(false);
    });

    it('should handle stream map retrieval errors', async () => {
      configManagerService.getMapIdByStream.mockRejectedValue(new Error('Stream error'));

      await expect(controller.streamToMap(streamName)).rejects.toThrow(HttpException);
    });
  });

  describe('getMaps', () => {
    it('should return all map configurations', async () => {
      configManagerService.getAllMapConfigs.mockResolvedValue(mockConfig.maps);

      const result = await controller.getMaps();

      expect(result.success).toBe(true);
      expect(result.data.maps).toHaveLength(1);
      expect(result.data.count).toBe(1);
      expect(configManagerService.getAllMapConfigs).toHaveBeenCalled();
    });

    it('should handle map retrieval errors', async () => {
      configManagerService.getAllMapConfigs.mockRejectedValue(new Error('Maps error'));

      await expect(controller.getMaps()).rejects.toThrow(HttpException);
    });
  });

  describe('error handling', () => {
    it('should throw HttpException with INTERNAL_SERVER_ERROR status', async () => {
      configManagerService.getConfig.mockRejectedValue(new Error('Test error'));

      try {
        await controller.getCurrentConfig();
      } catch (error) {
        expect(error).toBeInstanceOf(HttpException);
        expect((error as any).getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
      }
    });

    it('should preserve error messages in HttpException', async () => {
      const errorMessage = 'Specific error message';
      configManagerService.syncConfig.mockRejectedValue(new Error(errorMessage));

      try {
        await controller.syncConfig();
      } catch (error) {
        expect(error).toBeInstanceOf(HttpException);
        expect((error as any).getResponse()).toMatchObject({
          success: false,
          error: errorMessage
        });
      }
    });
  });
});