/**
 * MCP 配置加载和验证测试
 */

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import {
  loadMCPConfig,
  validateMCPConfig,
  normalizeMCPConfig,
  getEnabledServers,
  isToolEnabled,
  resolveEnvVariables,
  resolveEnvInObject,
} from '../../../src/mcp/config.js';
import type { MCPConfig } from '../../../src/mcp/types.js';

// Mock fs 模块
vi.mock('fs');
vi.mock('path', async () => {
  const actual = await vi.importActual('path');
  return {
    ...actual,
    extname: (actual as typeof import('path')).extname,
  };
});

describe('MCP Config', () => {
  beforeEach(() => {
    vi.resetAllMocks();
    // 默认不存在配置文件
    vi.mocked(fs.existsSync).mockReturnValue(false);
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('resolveEnvVariables', () => {
    it('应该解析环境变量引用', () => {
      process.env.TEST_VAR = 'test_value';
      const result = resolveEnvVariables('{env:TEST_VAR}');
      expect(result).toBe('test_value');
      delete process.env.TEST_VAR;
    });

    it('应该处理多个环境变量', () => {
      process.env.VAR1 = 'value1';
      process.env.VAR2 = 'value2';
      const result = resolveEnvVariables('{env:VAR1}/{env:VAR2}');
      expect(result).toBe('value1/value2');
      delete process.env.VAR1;
      delete process.env.VAR2;
    });

    it('应该对未设置的变量返回空字符串', () => {
      const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
      const result = resolveEnvVariables('{env:NONEXISTENT_VAR}');
      expect(result).toBe('');
      expect(warnSpy).toHaveBeenCalled();
      warnSpy.mockRestore();
    });

    it('应该保留普通字符串不变', () => {
      const result = resolveEnvVariables('plain string');
      expect(result).toBe('plain string');
    });
  });

  describe('resolveEnvInObject', () => {
    it('应该递归解析对象中的环境变量', () => {
      process.env.TEST_KEY = 'secret';
      const obj = {
        key: '{env:TEST_KEY}',
        nested: {
          value: '{env:TEST_KEY}',
        },
        array: ['{env:TEST_KEY}'],
      };
      const result = resolveEnvInObject(obj);
      expect(result).toEqual({
        key: 'secret',
        nested: {
          value: 'secret',
        },
        array: ['secret'],
      });
      delete process.env.TEST_KEY;
    });

    it('应该保留非字符串值不变', () => {
      const obj = {
        number: 123,
        boolean: true,
        null: null,
      };
      const result = resolveEnvInObject(obj);
      expect(result).toEqual(obj);
    });
  });

  describe('validateMCPConfig', () => {
    it('应该接受空配置', () => {
      const errors = validateMCPConfig({});
      expect(errors).toHaveLength(0);
    });

    it('应该接受有效的本地服务器配置', () => {
      const config: MCPConfig = {
        mcp: {
          filesystem: {
            type: 'local',
            command: ['npx', '-y', '@anthropic-ai/mcp-server-filesystem'],
          },
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors).toHaveLength(0);
    });

    it('应该接受有效的远程服务器配置', () => {
      const config: MCPConfig = {
        mcp: {
          remote: {
            type: 'remote',
            url: 'https://mcp.example.com',
          },
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors).toHaveLength(0);
    });

    it('应该拒绝无效的服务器名称', () => {
      const config: MCPConfig = {
        mcp: {
          '123invalid': {
            type: 'local',
            command: ['echo'],
          },
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('服务器名称');
    });

    it('应该拒绝缺少 command 的本地服务器', () => {
      const config: MCPConfig = {
        mcp: {
          server: {
            type: 'local',
            command: [],
          },
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('command');
    });

    it('应该拒绝无效的 URL', () => {
      const config: MCPConfig = {
        mcp: {
          remote: {
            type: 'remote',
            url: 'invalid-url',
          },
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('url');
    });

    it('应该验证 tools 配置', () => {
      const config: MCPConfig = {
        mcp: {},
        tools: {
          'valid-tool': true,
          'invalid-tool': 'not-boolean' as unknown as boolean,
        },
      };
      const errors = validateMCPConfig(config);
      expect(errors.length).toBeGreaterThan(0);
      expect(errors[0]).toContain('布尔值');
    });
  });

  describe('normalizeMCPConfig', () => {
    it('应该添加默认值', () => {
      const config: MCPConfig = {
        mcp: {
          server: {
            type: 'local',
            command: ['echo'],
          },
        },
      };
      const normalized = normalizeMCPConfig(config);
      expect(normalized.mcp?.server.enabled).toBe(true);
      expect(normalized.mcp?.server.timeout).toBe(30000);
    });

    it('应该保留显式设置的值', () => {
      const config: MCPConfig = {
        mcp: {
          server: {
            type: 'local',
            command: ['echo'],
            enabled: false,
            timeout: 5000,
          },
        },
      };
      const normalized = normalizeMCPConfig(config);
      expect(normalized.mcp?.server.enabled).toBe(false);
      expect(normalized.mcp?.server.timeout).toBe(5000);
    });
  });

  describe('getEnabledServers', () => {
    it('应该返回启用的服务器', () => {
      const config: MCPConfig = {
        mcp: {
          enabled: {
            type: 'local',
            command: ['echo'],
            enabled: true,
          },
          disabled: {
            type: 'local',
            command: ['echo'],
            enabled: false,
          },
          default: {
            type: 'local',
            command: ['echo'],
          },
        },
      };
      const servers = getEnabledServers(config);
      expect(servers).toHaveLength(2);
      expect(servers.map((s) => s.name)).toContain('enabled');
      expect(servers.map((s) => s.name)).toContain('default');
      expect(servers.map((s) => s.name)).not.toContain('disabled');
    });

    it('应该处理空配置', () => {
      const servers = getEnabledServers({});
      expect(servers).toHaveLength(0);
    });
  });

  describe('isToolEnabled', () => {
    it('没有配置时默认启用', () => {
      expect(isToolEnabled('any-tool')).toBe(true);
      expect(isToolEnabled('any-tool', undefined)).toBe(true);
    });

    it('应该精确匹配工具名', () => {
      const config = {
        'server-tool': false,
        'server-other': true,
      };
      expect(isToolEnabled('server-tool', config)).toBe(false);
      expect(isToolEnabled('server-other', config)).toBe(true);
    });

    it('应该支持通配符匹配', () => {
      const config = {
        'server-*': false,
      };
      expect(isToolEnabled('server-tool1', config)).toBe(false);
      expect(isToolEnabled('server-tool2', config)).toBe(false);
      expect(isToolEnabled('other-tool', config)).toBe(true);
    });

    it('精确匹配应优先于通配符', () => {
      const config = {
        'server-*': false,
        'server-special': true,
      };
      expect(isToolEnabled('server-special', config)).toBe(true);
      expect(isToolEnabled('server-other', config)).toBe(false);
    });
  });

  describe('loadMCPConfig', () => {
    it('没有配置文件时返回空配置', () => {
      vi.mocked(fs.existsSync).mockReturnValue(false);
      const config = loadMCPConfig('/test/dir');
      expect(config).toEqual({});
    });

    it('应该加载 JSON 配置文件', () => {
      const testConfig = {
        mcp: {
          server: {
            type: 'local',
            command: ['echo'],
          },
        },
      };
      vi.mocked(fs.existsSync).mockImplementation((filePath) => {
        return String(filePath).endsWith('config.json');
      });
      vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(testConfig));

      const config = loadMCPConfig('/test/dir');
      expect(config.mcp).toBeDefined();
    });

    it('应该合并用户级和项目级配置', () => {
      const userConfig = {
        mcp: {
          userServer: {
            type: 'local',
            command: ['user-cmd'],
          },
        },
      };
      const projectConfig = {
        mcp: {
          projectServer: {
            type: 'local',
            command: ['project-cmd'],
          },
        },
      };

      let callCount = 0;
      vi.mocked(fs.existsSync).mockImplementation((filePath) => {
        return (
          String(filePath).includes('.ai-assist') &&
          String(filePath).endsWith('config.json')
        );
      });
      vi.mocked(fs.readFileSync).mockImplementation(() => {
        callCount++;
        // 第一次调用返回用户配置，第二次返回项目配置
        return JSON.stringify(callCount === 1 ? userConfig : projectConfig);
      });

      const config = loadMCPConfig('/test/dir');
      expect(config.mcp?.userServer).toBeDefined();
      expect(config.mcp?.projectServer).toBeDefined();
    });
  });
});
