import { Test, TestingModule } from '@nestjs/testing';
import { ChatService } from './chat.service';
import { ElasticsearchService } from '../elasticsearch/elasticsearch.service';
import { EmbeddingService } from '../knowledge-base/embedding.service';
import { ModelConfigService } from '../model-config/model-config.service';
import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
import { SearchHistoryService } from '../search-history/search-history.service';
import { ConfigService } from '@nestjs/config';
import { RagService } from '../rag/rag.service';
import { I18nService } from '../i18n/i18n.service';
import { TenantService } from '../tenant/tenant.service';
import { UserSettingService } from '../user/user-setting.service';
import { ChatStreamChunk } from '../types';

describe('ChatService', () => {
  let service: ChatService;

  const mockElasticsearchService = {
    hybridSearch: jest.fn(),
  };

  const mockEmbeddingService = {
    getEmbeddings: jest.fn(),
  };

  const mockModelConfigService = {
    findOne: jest.fn(),
    findDefaultByType: jest.fn(),
  };

  const mockKnowledgeGroupService = {
    getFileIdsByGroups: jest.fn(),
  };

  const mockSearchHistoryService = {
    create: jest.fn(),
    addMessage: jest.fn(),
    findOne: jest.fn(),
    updateTitle: jest.fn(),
  };

  const mockConfigService = {
    get: jest.fn(),
  };

  const mockRagService = {
    searchKnowledge: jest.fn(),
  };

  const mockI18nService = {
    getMessage: jest.fn(),
    formatMessage: jest.fn(),
    getPrompt: jest.fn(),
    getChatTitlePrompt: jest.fn(),
  };

  const mockTenantService = {
    getSettings: jest.fn(),
  };

  const mockUserSettingService = {
    getByUser: jest.fn(),
  };

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ChatService,
        { provide: ElasticsearchService, useValue: mockElasticsearchService },
        { provide: EmbeddingService, useValue: mockEmbeddingService },
        { provide: ModelConfigService, useValue: mockModelConfigService },
        { provide: KnowledgeGroupService, useValue: mockKnowledgeGroupService },
        { provide: SearchHistoryService, useValue: mockSearchHistoryService },
        { provide: ConfigService, useValue: mockConfigService },
        { provide: RagService, useValue: mockRagService },
        { provide: I18nService, useValue: mockI18nService },
        { provide: TenantService, useValue: mockTenantService },
        { provide: UserSettingService, useValue: mockUserSettingService },
      ],
    }).compile();

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

  describe('processStreamWithThinking', () => {
    it('should detect and extract thinking content with <thinking> tags', async () => {
      // Mock stream with thinking tags
      const mockStream = async function* () {
        yield { content: '<thinking>' };
        yield { content: 'This is my reasoning process.' };
        yield { content: '</thinking>' };
        yield { content: 'This is the final answer.' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'thinking', data: 'This is my reasoning process.' },
        { type: 'content', data: 'This is the final answer.' },
      ]);
    });

    it('should detect and extract thinking content with code block format', async () => {
      // Mock stream with code block thinking format
      const mockStream = async function* () {
        yield { content: '```thinking\nLet me think about this.\n```\nHere is my answer.' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'thinking', data: '\nLet me think about this.\n' },
        { type: 'content', data: '\nHere is my answer.' },
      ]);
    });

    it('should detect and extract thinking content with bracket format', async () => {
      // Mock stream with bracket thinking format
      const mockStream = async function* () {
        yield { content: '[THINKING]' };
        yield { content: 'Analyzing the problem.' };
        yield { content: '[/THINKING]' };
        yield { content: 'The solution is...' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'thinking', data: 'Analyzing the problem.' },
        { type: 'content', data: 'The solution is...' },
      ]);
    });

    it('should handle regular content without thinking tags', async () => {
      // Mock stream without thinking tags
      const mockStream = async function* () {
        yield { content: 'This is a regular response.' };
        yield { content: ' No thinking here.' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'content', data: 'This is a regular response. No thinking here.' },
      ]);
    });

    it('should handle empty stream', async () => {
      // Mock empty stream
      const mockStream = async function* () {
        // Empty stream
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([]);
    });

    it('should handle stream with only thinking content', async () => {
      // Mock stream with only thinking
      const mockStream = async function* () {
        yield { content: '<thinking>' };
        yield { content: 'Only thinking, no answer.' };
        yield { content: '</thinking>' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'thinking', data: 'Only thinking, no answer.' },
      ]);
    });

    it('should handle small content that does not exceed 100 char threshold', async () => {
      // Mock stream with small content
      const mockStream = async function* () {
        yield { content: 'Short answer.' };
      };

      const chunks: ChatStreamChunk[] = [];
      for await (const chunk of service['processStreamWithThinking'](mockStream())) {
        chunks.push(chunk);
      }

      expect(chunks).toEqual([
        { type: 'content', data: 'Short answer.' },
      ]);
    });
  });

  describe('extractThinking', () => {
    it('should extract thinking from <thinking> tags', () => {
      const response = '<thinking>My reasoning</thinking>The answer';
      const result = service['extractThinking'](response);

      expect(result).toEqual({
        thinking: 'My reasoning',
        content: 'The answer',
      });
    });

    it('should extract thinking from code block format', () => {
      const response = '```thinking\nMy reasoning\n```\nThe answer';
      const result = service['extractThinking'](response);

      expect(result).toEqual({
        thinking: 'My reasoning',
        content: 'The answer',
      });
    });

    it('should extract thinking from bracket format', () => {
      const response = '[THINKING]My reasoning[/THINKING]The answer';
      const result = service['extractThinking'](response);

      expect(result).toEqual({
        thinking: 'My reasoning',
        content: 'The answer',
      });
    });

    it('should return empty thinking for regular content', () => {
      const response = 'Just a regular response';
      const result = service['extractThinking'](response);

      expect(result).toEqual({
        thinking: '',
        content: 'Just a regular response',
      });
    });
  });

  describe('detectThinkingFormat', () => {
    it('should detect xml format', () => {
      const buffer = '<thinking>test';
      const format = service['detectThinkingFormat'](buffer);

      expect(format).toBe('xml');
    });

    it('should detect code block format', () => {
      const buffer = '```thinking\ntest';
      const format = service['detectThinkingFormat'](buffer);

      expect(format).toBe('codeBlock');
    });

    it('should detect bracket format', () => {
      const buffer = '[THINKING]test';
      const format = service['detectThinkingFormat'](buffer);

      expect(format).toBe('bracket');
    });

    it('should return null for no thinking format', () => {
      const buffer = 'regular content';
      const format = service['detectThinkingFormat'](buffer);

      expect(format).toBeNull();
    });
  });
});
