import { Injectable, Logger } from '@nestjs/common';
import { ElasticsearchService } from '../elasticsearch/elasticsearch.service';
import { EmbeddingService } from '../knowledge-base/embedding.service';
import { RerankService } from './rerank.service';
import { I18nService } from '../i18n/i18n.service';
import { DEFAULT_LANGUAGE } from '../common/constants';

export interface RagSearchResult {
  id: string;
  score: number;
  content: string;
  fileId: string;
  fileName: string;
  title: string;
  chunkIndex: number;
  startPosition?: number;
  endPosition?: number;
}

@Injectable()
export class RagService {
  private readonly logger = new Logger(RagService.name);

  constructor(
    private readonly elasticsearchService: ElasticsearchService,
    private readonly embeddingService: EmbeddingService,
    private readonly rerankService: RerankService,
    private readonly i18nService: I18nService,
  ) {}

  /**
   * Main research method for RAG.
   * Supports hybrid search, reranking, query expansion, and HyDE.
   */
  async searchKnowledge(
    query: string,
    userId: string,
    topK: number = 5,
    similarityThreshold: number = 0.0,
    embeddingModelId?: string,
    enableFullTextSearch: boolean = true,
    enableRerank: boolean = false,
    rerankModelId?: string,
    selectedGroups?: string[],
    selectedFiles?: string[],
    rerankThreshold: number = 0.0,
    tenantId?: string,
    enableQueryExpansion: boolean = false,
    enableHyDE: boolean = false,
    language: string = DEFAULT_LANGUAGE,
  ): Promise<RagSearchResult[]> {
    this.logger.log(
      `RAG Search: query="${query}", rerank=${enableRerank}, tenantId=${tenantId}`,
    );

    // 1. Get embedding for the query if needed
    let queryVector: number[] = [];
    if (embeddingModelId) {
      try {
        const vectors = await this.embeddingService.getEmbeddings(
          [query],
          embeddingModelId,
        );
        queryVector = vectors[0];
      } catch (error) {
        this.logger.error('Failed to generate query embedding', error);
      }
    }

    // 2. Perform search via Elasticsearch
    // Note: ElasticsearchService.hybridSearch supports explicitFileIds
    const searchResults = await this.elasticsearchService.hybridSearch(
      queryVector,
      query,
      userId,
      topK * 2, // Get more results for reranking
      0.7, // Default vector weight
      selectedGroups,
      selectedFiles,
      tenantId,
    );

    let finalResults = searchResults;

    // 3. Apply Threshold
    finalResults = finalResults.filter((r) => r.score >= similarityThreshold);

    // 4. Perform Reranking if enabled
    if (enableRerank && rerankModelId && finalResults.length > 0) {
      try {
        // Map search results to string array for reranking
        const documentTexts = finalResults.map((r) => r.content);

        const rerankedPairs = await this.rerankService.rerank(
          query,
          documentTexts,
          userId,
          rerankModelId,
          topK,
          tenantId,
        );

        // Map reranked results back to RagSearchResult
        finalResults = rerankedPairs
          .filter((pair) => pair.score >= rerankThreshold)
          .map((pair) => {
            const originalResult = finalResults[pair.index];
            return {
              ...originalResult,
              score: pair.score, // Update with rerank score
            };
          });
      } catch (error) {
        this.logger.error(
          'Reranking failed, falling back to original results',
          error,
        );
      }
    }

    // 5. Final Slice
    return finalResults.slice(0, topK);
  }

  /**
   * Extract unique document names as sources
   */
  extractSources(results: RagSearchResult[]): string[] {
    const sources = new Set<string>();
    results.forEach((r) => {
      if (r.fileName) sources.add(r.fileName);
    });
    return Array.from(sources);
  }

  /**
   * Build the RAG prompt with instructions in the correct language
   */
  buildRagPrompt(
    query: string,
    searchResults: RagSearchResult[],
    language: string = DEFAULT_LANGUAGE,
  ): string {
    const normalizedLang = this.i18nService.normalizeLanguage(language);

    // Build context
    let context = '';
    if (searchResults.length === 0) {
      context =
        normalizedLang === 'zh'
          ? '未找到相关信息。'
          : normalizedLang === 'ja'
            ? '関連情報が見つかりませんでした。'
            : 'No relevant information found.';
    } else {
      searchResults.forEach((result, index) => {
        context += `[${index + 1}] File: ${result.fileName} (Score: ${result.score.toFixed(4)})\nContent: ${result.content}\n\n`;
      });
    }

    // Get localized prompt template from I18nService
    const promptTemplate = this.i18nService.getPrompt(
      language,
      'withContext',
      false,
    );

    return promptTemplate
      .replace('{context}', context)
      .replace('{history}', '') // History placeholders are usually handled in ChatService
      .replace('{question}', query);
  }
}
