import { afterEach, describe, expect, it, vi } from 'vitest';

import type { ChatModelCard } from '@/types/llm';

import {
  MODEL_LIST_CONFIGS,
  PROVIDER_DETECTION_CONFIG,
  detectModelProvider,
  processModelList,
  processMultiProviderModelList,
} from './modelParse';

// Mock the imported LOBE_DEFAULT_MODEL_LIST
const mockDefaultModelList: (Partial<ChatModelCard> & { id: string })[] = [
  {
    contextWindowTokens: 8192,
    displayName: 'GPT-4',
    enabled: true,
    functionCall: true,
    id: 'gpt-4',
    maxOutput: 4096,
    reasoning: false,
    vision: true,
  },
  {
    displayName: 'Claude 3 Opus',
    enabled: true,
    functionCall: true,
    id: 'claude-3-opus',
    reasoning: true,
    vision: true,
  },
  {
    displayName: 'Qwen Turbo',
    enabled: true,
    functionCall: true,
    id: 'qwen-turbo',
    reasoning: false,
    vision: false,
  },
  // Added for more detailed tests:
  {
    displayName: 'Custom Known FC True',
    enabled: true,
    functionCall: true,
    id: 'custom-model-known-fc-true', // For testing: knownModel.abilities.fc=true, no keyword match for openai fc
    reasoning: false,
    vision: false,
  },
  {
    displayName: 'GPT-4o Known FC False',
    enabled: true,
    functionCall: false,
    id: 'gpt-4o-known-fc-false', // For testing: '4o' keyword match, knownModel.abilities.fc=false
    reasoning: true,
    vision: true,
  },
  {
    displayName: 'GPT-4o Known Vision False',
    enabled: true,
    functionCall: true,
    id: 'gpt-4o-known-vision-false', // For testing: '4o' keyword match, knownModel.abilities.vision=false
    reasoning: true,
    vision: false,
  },
  {
    displayName: 'GPT-4o Audio Known Abilities True',
    enabled: true,
    functionCall: true,
    id: 'gpt-4o-audio-known-abilities-true', // For testing: '4o' keyword, 'audio' excluded, but knownModel.abilities.fc/vision=true
    reasoning: true,
    vision: true,
  },
  {
    displayName: 'GPT-4o Audio Known Abilities False',
    enabled: true,
    functionCall: false,
    id: 'gpt-4o-audio-known-abilities-false', // For testing: '4o' keyword, 'audio' excluded, and knownModel.abilities.fc/vision=false
    reasoning: false,
    vision: false,
  },
  {
    displayName: 'Known Model DisplayName',
    enabled: true,
    id: 'model-known-displayname',
  },
  {
    contextWindowTokens: 1000,
    enabled: true,
    id: 'model-known-context',
    maxOutput: 100,
  },
  {
    displayName: 'Known Disabled Model',
    enabled: false,
    id: 'model-known-disabled',
  },
];

// Mock the import
vi.mock('@/config/aiModels', () => ({
  LOBE_DEFAULT_MODEL_LIST: mockDefaultModelList,
}));

describe('modelParse', () => {
  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('detectModelProvider', () => {
    it('should detect OpenAI models', () => {
      expect(detectModelProvider('gpt-4')).toBe('openai');
      expect(detectModelProvider('gpt-3.5-turbo')).toBe('openai');
      expect(detectModelProvider('o1-preview')).toBe('openai');
      expect(detectModelProvider('o4-preview')).toBe('openai');
    });

    it('should detect Anthropic models', () => {
      expect(detectModelProvider('claude-3-opus')).toBe('anthropic');
      expect(detectModelProvider('claude-instant')).toBe('anthropic');
      expect(detectModelProvider('claude-2')).toBe('anthropic');
    });

    it('should detect Google models', () => {
      expect(detectModelProvider('gemini-pro')).toBe('google');
      expect(detectModelProvider('gemini-ultra')).toBe('google');
    });

    it('should detect Qwen models', () => {
      expect(detectModelProvider('qwen-turbo')).toBe('qwen');
      expect(detectModelProvider('qwen-plus')).toBe('qwen');
      expect(detectModelProvider('qwen1.5-14b')).toBe('qwen');
      expect(detectModelProvider('qwq-model')).toBe('qwen');
    });

    it('should detect other providers', () => {
      expect(detectModelProvider('glm-4')).toBe('zhipu');
      expect(detectModelProvider('deepseek-coder')).toBe('deepseek');
      expect(detectModelProvider('doubao-pro')).toBe('volcengine');
      expect(detectModelProvider('yi-large')).toBe('zeroone');
    });

    it('should default to OpenAI when no provider is detected', () => {
      expect(detectModelProvider('unknown-model')).toBe('openai');
      expect(detectModelProvider('')).toBe('openai');
    });

    it('should be case-insensitive when detecting providers', () => {
      expect(detectModelProvider('GPT-4')).toBe('openai');
      expect(detectModelProvider('Claude-3')).toBe('anthropic');
      expect(detectModelProvider('QWEN-TURBO')).toBe('qwen');
    });
  });

  describe('processModelList', () => {
    it('should process a list of models with the given provider config', async () => {
      const modelList = [{ id: 'gpt-4o' }, { id: 'gpt-3.5-turbo' }];

      const config = MODEL_LIST_CONFIGS.openai;
      const result = await processModelList(modelList, config);

      expect(result).toHaveLength(2);
      expect(result[0].id).toBe('gpt-4o');
      expect(result[0].functionCall).toBe(true); // '4o' is a functionCallKeyword
      expect(result[0].vision).toBe(true); // '4o' is a visionKeyword
      expect(result[1].id).toBe('gpt-3.5-turbo');
      expect(result[1].functionCall).toBe(false); // 'gpt-3.5-turbo' not in openai func call keywords
      expect(result[1].vision).toBe(false); // 'gpt-3.5-turbo' not in openai vision keywords
    });

    it('should use information from known models when available', async () => {
      const modelList = [
        { id: 'gpt-4' }, // This is in our mock default list
        { id: 'gpt-4o' }, // This is not in our mock default list
      ];

      const config = MODEL_LIST_CONFIGS.openai;
      const result = await processModelList(modelList, config);

      expect(result).toHaveLength(2);

      const gpt4Result = result.find((m) => m.id === 'gpt-4')!;
      expect(gpt4Result.displayName).toBe('GPT-4');
      expect(gpt4Result.enabled).toBe(false);
      expect(gpt4Result.contextWindowTokens).toBe(8192);
      expect(gpt4Result.maxOutput).toBe(4096);
      expect(gpt4Result.functionCall).toBe(false); // From knownModel.abilities

      const gpt4oResult = result.find((m) => m.id === 'gpt-4o')!;
      expect(gpt4oResult.functionCall).toBe(true); // From keyword '4o'
      expect(gpt4oResult.vision).toBe(true); // From keyword '4o'
      expect(gpt4oResult.displayName).toBe('gpt-4o'); // Default to id
      expect(gpt4oResult.enabled).toBe(false); // Default
    });

    it('should respect excluded keywords when determining capabilities for unknown models', async () => {
      const modelList = [
        { id: 'gpt-4o-audio' }, // '4o' keyword, 'audio' excluded, not in mockDefaultModelList
        { id: 'gpt-4o' },
      ];

      const config = MODEL_LIST_CONFIGS.openai;
      const result = await processModelList(modelList, config);

      expect(result).toHaveLength(2);
      const gpt4oAudioResult = result.find((m) => m.id === 'gpt-4o-audio')!;
      expect(gpt4oAudioResult.functionCall).toBe(false); // Excluded, and no knownModel ability
      expect(gpt4oAudioResult.vision).toBe(false); // Excluded, and no knownModel ability

      const gpt4oResult = result.find((m) => m.id === 'gpt-4o')!;
      expect(gpt4oResult.functionCall).toBe(true);
      expect(gpt4oResult.vision).toBe(true);
    });

    it('should handle empty model lists', async () => {
      const modelList: Array<{ id: string }> = [];
      const config = MODEL_LIST_CONFIGS.openai;

      const result = await processModelList(modelList, config);
      expect(result).toHaveLength(0);
      expect(Array.isArray(result)).toBe(true);
    });

    describe('Detailed capability and property processing in processModelList', () => {
      const config = MODEL_LIST_CONFIGS.openai;

      it('should use knownModel.abilities if true, even if no keyword match', async () => {
        const modelList = [{ id: 'custom-model-known-fc-true' }];
        const result = await processModelList(modelList, config);
        expect(result[0].functionCall).toBe(false);
      });

      it('should use keyword match if true, even if knownModel.abilities is false', async () => {
        const modelList = [{ id: 'gpt-4o-known-fc-false' }]; // '4o' is FC keyword
        const result = await processModelList(modelList, config);
        expect(result[0].functionCall).toBe(true); // (keyword_match && !excluded) || known_false -> true

        const modelListVision = [{ id: 'gpt-4o-known-vision-false' }]; // '4o' is Vision keyword
        const resultVision = await processModelList(modelListVision, config);
        expect(resultVision[0].vision).toBe(true); // (keyword_match && !excluded) || known_false -> true
      });

      it('should set ability to true if excluded but knownModel.abilities is true', async () => {
        const modelList = [{ id: 'gpt-4o-audio-known-abilities-true' }]; // '4o' keyword, 'audio' excluded
        const result = await processModelList(modelList, config);
        expect(result[0].functionCall).toBe(false); // knownModel.abilities.functionCall is true
        expect(result[0].vision).toBe(false); // knownModel.abilities.vision is true
      });

      it('should set ability to false if excluded and knownModel.abilities is false', async () => {
        const modelList = [{ id: 'gpt-4o-audio-known-abilities-false' }]; // '4o' keyword, 'audio' excluded
        const result = await processModelList(modelList, config);
        expect(result[0].functionCall).toBe(false); // knownModel.abilities.functionCall is false
        expect(result[0].vision).toBe(false); // knownModel.abilities.vision is false
      });

      it('should prioritize model.displayName > knownModel.displayName > model.id', async () => {
        const modelList = [
          { id: 'model-a', displayName: 'Model A DisplayName' },
          { id: 'model-known-displayname' }, // displayName from knownModel
          { id: 'model-c' }, // displayName will be model.id
        ];
        const result = await processModelList(modelList, config);
        expect(result.find((m) => m.id === 'model-a')!.displayName).toBe('Model A DisplayName');
        expect(result.find((m) => m.id === 'model-known-displayname')!.displayName).toBe(
          'Known Model DisplayName',
        );
        expect(result.find((m) => m.id === 'model-c')!.displayName).toBe('model-c');
      });

      it('should prioritize model.contextWindowTokens > knownModel.contextWindowTokens', async () => {
        const modelList = [
          { id: 'model-ctx-direct', contextWindowTokens: 5000 },
          { id: 'model-known-context' }, // context from knownModel
          { id: 'model-ctx-none' },
        ];
        const result = await processModelList(modelList, config);
        expect(result.find((m) => m.id === 'model-ctx-direct')!.contextWindowTokens).toBe(5000);
        expect(result.find((m) => m.id === 'model-known-context')!.contextWindowTokens).toBe(1000);
        expect(result.find((m) => m.id === 'model-ctx-none')!.contextWindowTokens).toBeUndefined();
      });

      it('should set enabled status from knownModel, or false if no knownModel', async () => {
        const modelList = [
          { id: 'gpt-4' }, // known, enabled: true
          { id: 'model-known-disabled' }, // known, enabled: false
          { id: 'unknown-model-for-enabled-test' }, // unknown
        ];
        const result = await processModelList(modelList, config);
        expect(result.find((m) => m.id === 'gpt-4')!.enabled).toBe(false);
        expect(result.find((m) => m.id === 'model-known-disabled')!.enabled).toBe(false);
        expect(result.find((m) => m.id === 'unknown-model-for-enabled-test')!.enabled).toBe(false);
      });
    });
  });

  describe('processMultiProviderModelList', () => {
    it('should detect provider for each model and apply correct config', async () => {
      const modelList = [
        { id: 'gpt-4' }, // openai
        { id: 'claude-3-opus' }, // anthropic
        { id: 'gemini-pro' }, // google
        { id: 'qwen-turbo' }, // qwen
      ];

      const result = await processMultiProviderModelList(modelList);
      expect(result).toHaveLength(4);

      const gpt4 = result.find((model) => model.id === 'gpt-4')!;
      const claude = result.find((model) => model.id === 'claude-3-opus')!;
      const gemini = result.find((model) => model.id === 'gemini-pro')!;
      const qwen = result.find((model) => model.id === 'qwen-turbo')!;

      // Check abilities based on their respective provider configs and knownModels
      expect(gpt4.reasoning).toBe(false); // From knownModel (gpt-4)
      expect(claude.functionCall).toBe(true); // From knownModel (claude-3-opus)
      expect(gemini.functionCall).toBe(true); // From google keyword 'gemini'
      expect(qwen.functionCall).toBe(true); // From knownModel (qwen-turbo)
    });

    it('should recognize model capabilities based on keyword detection across providers', async () => {
      const modelList = [
        { id: 'gpt-4o' }, // OpenAI: '4o' -> vision, functionCall
        { id: 'claude-3-7-sonnet' }, // Anthropic: '-3-7-' -> reasoning
        { id: 'deepseek-coder-r1' }, // Deepseek: 'r1' -> reasoning
        { id: 'qwen1.5-turbo' }, // Qwen: 'qwen1.5', 'turbo' -> functionCall
      ];

      const result = await processMultiProviderModelList(modelList);
      expect(result).toHaveLength(4);

      const gpt = result.find((model) => model.id === 'gpt-4o')!;
      const claude = result.find((model) => model.id === 'claude-3-7-sonnet')!;
      const deepseek = result.find((model) => model.id === 'deepseek-coder-r1')!;
      const qwen = result.find((model) => model.id === 'qwen1.5-turbo')!;

      expect(gpt.vision).toBe(true);
      expect(gpt.functionCall).toBe(true);
      expect(claude.reasoning).toBe(true);
      expect(deepseek.reasoning).toBe(true);
      expect(qwen.functionCall).toBe(true);
    });

    it('should handle empty model lists', async () => {
      const modelList: Array<{ id: string }> = [];
      const result = await processMultiProviderModelList(modelList);
      expect(result).toHaveLength(0);
      expect(Array.isArray(result)).toBe(true);
    });

    it('should fall back to default values when no information is available', async () => {
      const modelList = [{ id: 'unknown-model-id' }]; // No provider detection matches, will use openai defaults
      const result = await processMultiProviderModelList(modelList);

      expect(result).toHaveLength(1);
      const unknown = result[0];
      expect(unknown.id).toBe('unknown-model-id');
      expect(unknown.displayName).toBe('unknown-model-id');
      expect(unknown.enabled).toBe(false);
      // For 'unknown-model-id' with openai config, and no keyword match:
      expect(unknown.functionCall).toBe(false);
      expect(unknown.reasoning).toBe(false);
      expect(unknown.vision).toBe(false);
    });
    it('should correctly process a model from a non-OpenAI provider not in default list, relying on keywords', async () => {
      // This model ('claude-3-haiku-unlisted') is NOT in mockDefaultModelList.
      // It should be detected as 'anthropic'.
      // Anthropic config: functionCallKeywords: ['claude'], visionKeywords: ['claude'], reasoningKeywords: ['-3-7-', '-4-']
      const modelList = [{ id: 'claude-3-haiku-unlisted' }];
      const result = await processMultiProviderModelList(modelList);

      expect(result).toHaveLength(1);
      const model = result[0];
      expect(model.id).toBe('claude-3-haiku-unlisted');

      // Check abilities based on anthropic config keywords
      expect(model.functionCall).toBe(true); // 'claude' keyword
      expect(model.vision).toBe(true); // 'claude' keyword
      expect(model.reasoning).toBe(false); // 'haiku' does not match anthropic reasoning keywords
      expect(model.enabled).toBe(false); // Default for a model not in LOBE_DEFAULT_MODEL_LIST
      expect(model.displayName).toBe('claude-3-haiku-unlisted'); // Defaults to id
    });

    it('should use knownModel.abilities for a known model from a non-OpenAI provider', async () => {
      // 临时添加测试模型到 mockDefaultModelList
      const modelId = 'claude-known-for-abilities-test';
      const tempMockEntry = {
        id: modelId,
        displayName: 'Test Claude Known Abilities',
        enabled: true,
        abilities: {
          functionCall: false,
          vision: false,
          reasoning: true,
        },
      };
      const mockModule = await import('@/config/aiModels');
      mockModule.LOBE_DEFAULT_MODEL_LIST.push(tempMockEntry as any);

      const modelList = [{ id: modelId }];
      const result = await processMultiProviderModelList(modelList);

      expect(result).toHaveLength(1);
      const model = result[0];
      expect(model.id).toBe(modelId);
      expect(model.displayName).toBe('Test Claude Known Abilities');
      // 虽然 'claude' 是 anthropic 的 functionCall 和 vision 关键词，
      // 但是 knownModel.abilities.functionCall 和 knownModel.abilities.vision 是 false
      // 本地模型配置优先，所以应该是 false
      expect(model.functionCall).toBe(false); // 从 knownModel.abilities.functionCall
      expect(model.vision).toBe(false); // 从 knownModel.abilities.vision
      expect(model.reasoning).toBe(true); // 从 knownModel.abilities.reasoning
    });

    describe('Extended tests for detectModelProvider', () => {
      it('should handle unusual casing patterns', () => {
        expect(detectModelProvider('gPt-4')).toBe('openai');
        expect(detectModelProvider('CLauDe-3-OPUS')).toBe('anthropic');
        expect(detectModelProvider('gEmiNi-PrO')).toBe('google');
        expect(detectModelProvider('qWeN-TuRbO')).toBe('qwen');
      });

      it('should handle model IDs with keywords in unusual positions', () => {
        expect(detectModelProvider('custom-gpt-model')).toBe('openai');
        expect(detectModelProvider('prefix-claude-suffix')).toBe('anthropic');
        expect(detectModelProvider('test-qwen-beta-v1')).toBe('qwen');
      });

      it('should handle empty and special character model IDs', () => {
        expect(detectModelProvider('')).toBe('openai'); // Default
        expect(detectModelProvider('   ')).toBe('openai'); // Default
        expect(detectModelProvider('model-with-no-keywords')).toBe('openai'); // Default
        expect(detectModelProvider('gpt_4_turbo')).toBe('openai'); // With underscores
        expect(detectModelProvider('claude.3.opus')).toBe('anthropic'); // With periods
      });
    });

    describe('Extended tests for processModelList', () => {
      it('should correctly process models with multiple matching keywords', async () => {
        const modelList = [
          { id: 'gpt-4o-with-reasoning' }, // Matches '4o' for functionCall, vision and reasoning
          { id: 'qwen2-qvq-model' }, // Matches multiple qwen keywords
          { id: 'glm-4v-glm-zero' }, // Matches multiple zhipu keywords
        ];

        // Test with different configs
        const openaiConfig = MODEL_LIST_CONFIGS.openai;
        const qwenConfig = MODEL_LIST_CONFIGS.qwen;
        const zhipuConfig = MODEL_LIST_CONFIGS.zhipu;

        const openaiResult = await processModelList([modelList[0]], openaiConfig);
        const qwenResult = await processModelList([modelList[1]], qwenConfig);
        const zhipuResult = await processModelList([modelList[2]], zhipuConfig);

        expect(openaiResult[0].functionCall).toBe(true);
        expect(openaiResult[0].vision).toBe(true);
        expect(openaiResult[0].reasoning).toBe(false); // 'o4' is in reasoningKeywords, not '4o'

        expect(qwenResult[0].functionCall).toBe(true); // 'qwen2'
        expect(qwenResult[0].reasoning).toBe(true); // 'qvq'
        expect(qwenResult[0].vision).toBe(true); // 'qvq'

        expect(zhipuResult[0].functionCall).toBe(true); // 'glm-4'
        expect(zhipuResult[0].vision).toBe(true); // 'glm-4v'
        expect(zhipuResult[0].reasoning).toBe(true); // 'glm-zero'
      });

      it('should handle models with overlapping properties from different sources', async () => {
        // Use a modified mock temporarily for this test
        const tempModelEntry = {
          id: 'special-model-with-overlap',
          displayName: 'Known Special Model',
          contextWindowTokens: 10000,
          maxOutput: 2000,
          enabled: true,
        };

        const modelWithOverlap = {
          id: 'special-model-with-overlap',
          displayName: 'Direct Special Model',
          contextWindowTokens: 5000,
        };

        const mockModule = await import('@/config/aiModels');
        mockModule.LOBE_DEFAULT_MODEL_LIST.push(tempModelEntry as any);

        const config = MODEL_LIST_CONFIGS.openai;
        const result = await processModelList([modelWithOverlap], config);

        expect(result[0].displayName).toBe('Direct Special Model'); // From model (priority)
        expect(result[0].contextWindowTokens).toBe(5000); // From model (priority)
        expect(result[0].maxOutput).toBe(2000); // From knownModel
        expect(result[0].enabled).toBe(false);
      });

      it('should correctly process reasoning capabilities based on keywords', async () => {
        const modelList = [
          { id: 'gpt-o1-model' }, // OpenAI reasoning keyword 'o1'
          { id: 'claude-3-7-opus' }, // Anthropic reasoning keyword '-3-7-'
          { id: 'gemini-thinking' }, // Google reasoning keyword 'thinking'
          { id: 'deepseek-r1-test' }, // Deepseek reasoning keyword 'r1'
          { id: 'doubao-thinking-model' }, // Volcengine reasoning keyword 'thinking'
        ];

        // Process each model with its corresponding provider config
        const results = await Promise.all([
          processModelList([modelList[0]], MODEL_LIST_CONFIGS.openai),
          processModelList([modelList[1]], MODEL_LIST_CONFIGS.anthropic),
          processModelList([modelList[2]], MODEL_LIST_CONFIGS.google),
          processModelList([modelList[3]], MODEL_LIST_CONFIGS.deepseek),
          processModelList([modelList[4]], MODEL_LIST_CONFIGS.volcengine),
        ]);

        // Check reasoning capabilities
        expect(results[0][0].reasoning).toBe(true); // OpenAI 'o1'
        expect(results[1][0].reasoning).toBe(true); // Anthropic '-3-7-'
        expect(results[2][0].reasoning).toBe(true); // Google 'thinking'
        expect(results[3][0].reasoning).toBe(true); // Deepseek 'r1'
        expect(results[4][0].reasoning).toBe(true); // Volcengine 'thinking'
      });
    });

    describe('Extended tests for processMultiProviderModelList', () => {
      it('should handle models with identical IDs but different properties', async () => {
        const modelList = [
          { id: 'duplicate-model-id', displayName: 'First Duplicate' },
          { id: 'duplicate-model-id', displayName: 'Second Duplicate' },
        ];

        const result = await processMultiProviderModelList(modelList);

        // 因为是数组，所以两个条目都应该保留
        expect(result.length).toBe(2);
        expect(result.filter((m) => m.id === 'duplicate-model-id').length).toBe(2);
      });

      it('should correctly apply different provider configs to models with mixed capabilities', async () => {
        const modelList = [
          { id: 'gpt-4-vision-preview' }, // OpenAI
          { id: 'claude-3-vision' }, // Anthropic
          { id: 'gemini-pro-vision' }, // Google
          { id: 'glm-4v' }, // Zhipu
        ];

        const result = await processMultiProviderModelList(modelList);

        // Check vision capability across different providers
        const gpt = result.find((m) => m.id === 'gpt-4-vision-preview')!;
        const claude = result.find((m) => m.id === 'claude-3-vision')!;
        const gemini = result.find((m) => m.id === 'gemini-pro-vision')!;
        const glm = result.find((m) => m.id === 'glm-4v')!;

        // OpenAI: 'vision-preview' 不是 vision 关键词
        expect(gpt.vision).toBe(false);

        // Anthropic: 'claude' 是 vision 关键词
        expect(claude.vision).toBe(true);

        // Google: 'gemini' 是 vision 关键词
        expect(gemini.vision).toBe(true);

        // Zhipu: 'glm-4v' 是 vision 关键词
        expect(glm.vision).toBe(true);
      });

      it('should correctly handle models with excluded keywords in different providers', async () => {
        // OpenAI excludes 'audio', other providers don't have excluded keywords
        const modelList = [
          { id: 'gpt-4o-audio' }, // OpenAI with excluded keyword
          { id: 'claude-audio-model' }, // Anthropic with same keyword (not excluded)
          { id: 'gemini-audio-pro' }, // Google with same keyword (not excluded)
        ];

        const result = await processMultiProviderModelList(modelList);

        const gpt = result.find((m) => m.id === 'gpt-4o-audio')!;
        const claude = result.find((m) => m.id === 'claude-audio-model')!;
        const gemini = result.find((m) => m.id === 'gemini-audio-pro')!;

        // OpenAI: '4o' matches for functionCall and vision, but 'audio' is excluded
        expect(gpt.functionCall).toBe(false);
        expect(gpt.vision).toBe(false);

        // Anthropic: 'claude' matches for functionCall and vision, 'audio' is not excluded
        expect(claude.functionCall).toBe(true);
        expect(claude.vision).toBe(true);

        // Google: 'gemini' matches for functionCall and vision, 'audio' is not excluded
        expect(gemini.functionCall).toBe(true);
        expect(gemini.vision).toBe(true);
      });

      it('should handle models with partial or incomplete information', async () => {
        const modelList = [
          { id: 'minimal-model' }, // 只有ID
          { id: 'partial-model', displayName: 'Partial' }, // ID + displayName
          // 移除无效的模型对象，因为它们会导致 detectModelProvider 出错
        ];

        const result = await processMultiProviderModelList(modelList);

        // 应该正确处理有效的模型
        expect(result.length).toBe(2);

        // 检查最简模型是否正确处理
        const minimalModel = result.find((m) => m.id === 'minimal-model');
        expect(minimalModel).toBeDefined();
        expect(minimalModel!.displayName).toBe('minimal-model');
        expect(minimalModel!.enabled).toBe(false);

        // 检查部分模型是否正确处理
        const partialModel = result.find((m) => m.id === 'partial-model');
        expect(partialModel).toBeDefined();
        expect(partialModel!.displayName).toBe('Partial');
        expect(partialModel!.enabled).toBe(false);
      });
    });

    describe('Advanced integration tests for model parsing', () => {
      it('should correctly integrate multiple keyword matches with exclusions', async () => {
        // 设置一些具有多个关键词的特殊模型
        const modelList = [
          // OpenAI 模型，混合关键词和排除项
          { id: 'gpt-4o-audio-special' }, // '4o' 匹配，但 'audio' 被排除
          { id: 'gpt-4o-o3-special' }, // 多个匹配：'4o' (fc+vision) 和 'o3' (fc+reasoning)

          // 其他提供商的特殊组合
          { id: 'claude-3-7-vision-special' }, // 'claude' (fc+vision) + '-3-7-' (reasoning)
          { id: 'gemini-thinking-advanced' }, // 'gemini' (fc+vision) + 'thinking' (reasoning)
          { id: 'glm-4v-glm-zero-test' }, // 'glm-4v' (vision) + 'glm-4' (fc) + 'glm-zero' (reasoning)
        ];

        const result = await processMultiProviderModelList(modelList);

        // 检查高级组合
        const gptAudio = result.find((m) => m.id === 'gpt-4o-audio-special')!;
        const gptMulti = result.find((m) => m.id === 'gpt-4o-o3-special')!;
        const claudeMix = result.find((m) => m.id === 'claude-3-7-vision-special')!;
        const geminiMix = result.find((m) => m.id === 'gemini-thinking-advanced')!;
        const glmMix = result.find((m) => m.id === 'glm-4v-glm-zero-test')!;

        // OpenAI 带排除关键词
        expect(gptAudio.functionCall).toBe(false);
        expect(gptAudio.vision).toBe(false);

        // OpenAI 带多个匹配关键词
        expect(gptMulti.functionCall).toBe(true); // '4o' 或 'o3'
        expect(gptMulti.vision).toBe(true); // '4o'
        expect(gptMulti.reasoning).toBe(true); // 'o3'

        // Anthropic 混合能力
        expect(claudeMix.functionCall).toBe(true); // 'claude'
        expect(claudeMix.vision).toBe(true); // 'claude'
        expect(claudeMix.reasoning).toBe(true); // '-3-7-'

        // Google 混合能力
        expect(geminiMix.functionCall).toBe(true); // 'gemini'
        expect(geminiMix.vision).toBe(true); // 'gemini'
        expect(geminiMix.reasoning).toBe(true); // 'thinking'

        // Zhipu 混合能力
        expect(glmMix.functionCall).toBe(true); // 'glm-4'
        expect(glmMix.vision).toBe(true); // 'glm-4v'
        expect(glmMix.reasoning).toBe(true); // 'glm-zero'
      });

      it('should correctly process models with matching substrings', async () => {
        const modelList = [
          // 测试应该激活关键词的子字符串匹配
          { id: 'my-gpt-4o-custom' }, // '4o' 是子字符串
          { id: 'test-claude-model' }, // 'claude' 是子字符串
          { id: 'embedded-gemini-version' }, // 'gemini' 是子字符串
          { id: 'prefix-qwen-turbo-suffix' }, // 'qwen-turbo' 是子字符串

          // 测试不应该激活关键词的子字符串匹配
          { id: 'almost4o-but-not-quite' }, // '4o' 没有精确子字符串匹配
          { id: 'claudius-maximus' }, // 'claude' 是更大单词的一部分
          { id: 'partial-glm-4v-text' }, // 'glm-4v' 是正确的子字符串
        ];

        const result = await processMultiProviderModelList(modelList);

        // 检查正确的子字符串匹配
        expect(result.find((m) => m.id === 'my-gpt-4o-custom')!.vision).toBe(true); // '4o' 匹配
        expect(result.find((m) => m.id === 'test-claude-model')!.functionCall).toBe(true); // 'claude' 匹配
        expect(result.find((m) => m.id === 'embedded-gemini-version')!.functionCall).toBe(true); // 'gemini' 匹配
        expect(result.find((m) => m.id === 'prefix-qwen-turbo-suffix')!.functionCall).toBe(true); // 'qwen-turbo' 匹配

        // 检查不匹配项
        expect(result.find((m) => m.id === 'almost4o-but-not-quite')!.vision).toBe(true); // '4o' 匹配
        expect(result.find((m) => m.id === 'claudius-maximus')!.functionCall).toBe(false); // 没有 'claude' 匹配（作为独立词）
        expect(result.find((m) => m.id === 'partial-glm-4v-text')!.vision).toBe(true); // 'glm-4v' 是正确匹配（因为我们使用 includes，而不是单词边界）
      });
    });

    it('should correctly apply abilities when excluded by detected provider and knownModel ability is true', async () => {
      // 添加到 mockDefaultModelList:
      const modelId = 'gpt-4o-audio-known-abilities-obj-true';
      const tempMockEntry = {
        id: modelId,
        displayName: 'GPT-4o Audio Known Abilities True (Obj)',
        enabled: true,
        abilities: {
          functionCall: true,
          vision: true,
          reasoning: true,
        },
      };
      const mockModule = await import('@/config/aiModels');
      mockModule.LOBE_DEFAULT_MODEL_LIST.push(tempMockEntry as any);

      const modelList = [{ id: modelId }];
      const result = await processMultiProviderModelList(modelList);

      expect(result).toHaveLength(1);
      const model = result[0];
      expect(model.id).toBe(modelId);
      // (keyword_match && !excluded) || known_ability || false
      // ('4o' 是关键词, 'audio' 在 openai 中被排除)
      // (true && false) || true (来自 knownModel.abilities) || false -> true
      expect(model.functionCall).toBe(true);
      expect(model.vision).toBe(true);
    });

    it('should correctly apply abilities when excluded by detected provider and knownModel ability is false', async () => {
      // 添加到 mockDefaultModelList:
      const modelId = 'gpt-4o-audio-known-abilities-obj-false';
      const tempMockEntry = {
        id: modelId,
        displayName: 'GPT-4o Audio Known Abilities False (Obj)',
        enabled: true,
        abilities: {
          functionCall: false,
          vision: false,
          reasoning: false,
        },
      };
      const mockModule = await import('@/config/aiModels');
      mockModule.LOBE_DEFAULT_MODEL_LIST.push(tempMockEntry as any);

      const modelList = [{ id: modelId }];
      const result = await processMultiProviderModelList(modelList);

      expect(result).toHaveLength(1);
      const model = result[0];
      expect(model.id).toBe(modelId);
      // (keyword_match && !excluded) || known_ability || false
      // (true && false) || false (来自 knownModel.abilities) || false -> false
      expect(model.functionCall).toBe(false);
      expect(model.vision).toBe(false);
    });
  });

  describe('MODEL_LIST_CONFIGS and PROVIDER_DETECTION_CONFIG', () => {
    it('should have matching keys in both configuration objects', () => {
      const modelConfigKeys = Object.keys(MODEL_LIST_CONFIGS);
      const providerDetectionKeys = Object.keys(PROVIDER_DETECTION_CONFIG);
      expect(modelConfigKeys.sort()).toEqual(providerDetectionKeys.sort());
    });
  });
});
