// src/services/ai/toolSelector.ts
import { config } from '../../config/appConfig.js';
import { aiService } from './aiService.js';
import { embeddingsService } from '../../utils/embeddings.js';
import { getPrompt } from '../../config/prompts.js';
import { logger } from '../logger.js';

export interface ToolSelection {
  selectedTools: any[];
  selectedConcepts: any[];
  selectionMethod: string;
  confidence: number;
}

export interface CandidateData {
  tools: any[];
  concepts: any[];
  selectionMethod: string;
}

export class ToolSelector {
  private readonly embeddingCandidates: number;
  private readonly similarityThreshold: number;
  private readonly embeddingSelectionLimit: number;
  private readonly embeddingConceptsLimit: number;
  private readonly noEmbeddingsToolLimit: number;
  private readonly noEmbeddingsConceptLimit: number;
  private readonly embeddingsMinTools: number;
  private readonly embeddingsMaxReductionRatio: number;
  private readonly methodSelectionRatio: number;
  private readonly softwareSelectionRatio: number;
  private readonly maxSelectedItems: number;

  constructor() {
    this.embeddingCandidates = config.ai.embeddingCandidates;
    this.similarityThreshold = config.ai.similarityThreshold;
    this.embeddingSelectionLimit = config.ai.embeddingSelectionLimit;
    this.embeddingConceptsLimit = config.ai.embeddingConceptsLimit;
    this.noEmbeddingsToolLimit = config.ai.noEmbeddingsToolLimit;
    this.noEmbeddingsConceptLimit = config.ai.noEmbeddingsConceptLimit;
    this.embeddingsMinTools = config.ai.embeddingsMinTools;
    this.embeddingsMaxReductionRatio = config.ai.embeddingsMaxReductionRatio;
    this.methodSelectionRatio = config.ai.methodSelectionRatio;
    this.softwareSelectionRatio = config.ai.softwareSelectionRatio;
    this.maxSelectedItems = config.ai.maxSelectedItems;
    
    logger.info('tool-selector', 'Tool selector initialized', {
      embeddingCandidates: this.embeddingCandidates,
      methodRatio: `${(this.methodSelectionRatio * 100).toFixed(0)}%`,
      softwareRatio: `${(this.softwareSelectionRatio * 100).toFixed(0)}%`
    });
  }

  async getCandidates(
    userQuery: string,
    toolsData: any
  ): Promise<CandidateData> {
    try {
      await embeddingsService.waitForInitialization();
    } catch (error) {
      logger.warn('tool-selector', 'Embeddings initialization failed, using full dataset');
    }
    
    if (embeddingsService.isEnabled()) {
      return this.getEmbeddingCandidates(userQuery, toolsData);
    } else {
      return this.getFullDatasetCandidates(toolsData);
    }
  }

  private async getEmbeddingCandidates(
    userQuery: string,
    toolsData: any
  ): Promise<CandidateData> {
    const startTime = Date.now();
    
    const similarItems = await embeddingsService.findSimilar(
      userQuery,
      this.embeddingCandidates,
      this.similarityThreshold
    );
    
    logger.embedding('similarity search completed', {
      query: userQuery.slice(0, 50),
      candidates: this.embeddingCandidates,
      threshold: this.similarityThreshold,
      results: similarItems.length,
      duration: `${Date.now() - startTime}ms`
    });
    
    const toolsMap = new Map(toolsData.tools.map((tool: any) => [tool.name, tool]));
    const conceptsMap = new Map(toolsData.concepts.map((concept: any) => [concept.name, concept]));
    
    const similarTools = similarItems
      .filter((item: any) => item.type === 'tool')
      .map((item: any) => toolsMap.get(item.name))
      .filter((tool: any): tool is NonNullable<any> => tool !== undefined && tool !== null);
    
    const similarConcepts = similarItems
      .filter((item: any) => item.type === 'concept')
      .map((item: any) => conceptsMap.get(item.name))
      .filter((concept: any): concept is NonNullable<any> => concept !== undefined && concept !== null);
    
    const totalAvailableTools = toolsData.tools.length;
    const reductionRatio = similarTools.length / totalAvailableTools;
    
    const useEmbeddings = similarTools.length >= this.embeddingsMinTools && 
                          reductionRatio <= this.embeddingsMaxReductionRatio;
    
    if (useEmbeddings) {
      logger.selection('embeddings', similarTools.length, similarConcepts.length, {
        reduction: `${totalAvailableTools} → ${similarTools.length}`,
        ratio: reductionRatio.toFixed(2)
      });
      
      return {
        tools: similarTools,
        concepts: similarConcepts,
        selectionMethod: 'embeddings_candidates'
      };
    } else {
      logger.info('tool-selector', 'Embeddings filtering insufficient, using full dataset', {
        toolsFound: similarTools.length,
        minRequired: this.embeddingsMinTools,
        reductionRatio: reductionRatio.toFixed(2),
        maxRatio: this.embeddingsMaxReductionRatio
      });
      
      return this.getFullDatasetCandidates(toolsData);
    }
  }

  private getFullDatasetCandidates(toolsData: any): CandidateData {
    logger.selection('full-dataset', toolsData.tools.length, toolsData.concepts.length);
    
    return {
      tools: toolsData.tools,
      concepts: toolsData.concepts,
      selectionMethod: 'full_dataset'
    };
  }

  async selectTools(
    userQuery: string,
    candidateTools: any[],
    candidateConcepts: any[],
    mode: string,
    selectionMethod: string
  ): Promise<ToolSelection> {
    const startTime = Date.now();
    
    const candidateMethods = candidateTools.filter((tool: any) => 
      tool && tool.type === 'method'
    );
    const candidateSoftware = candidateTools.filter((tool: any) => 
      tool && tool.type === 'software'
    );
    
    logger.info('tool-selector', 'Starting AI tool selection', {
      mode,
      selectionMethod,
      methods: candidateMethods.length,
      software: candidateSoftware.length,
      concepts: candidateConcepts.length
    });
    
    const { toolsToSend, conceptsToSend } = this.prepareSelectionData(
      candidateMethods,
      candidateSoftware,
      candidateConcepts,
      selectionMethod
    );

    const basePrompt = getPrompt(
      'toolSelection', 
      mode, 
      userQuery, 
      selectionMethod, 
      this.maxSelectedItems
    );
    
    const prompt = getPrompt(
      'toolSelectionWithData', 
      basePrompt, 
      toolsToSend, 
      conceptsToSend
    );

    if (!aiService.validatePromptSize(prompt, 35000)) {
      throw new Error('Prompt too large for AI model');
    }

    const response = await aiService.call({
      prompt,
      maxTokens: 2500,
      context: `tool-selection-${mode}`
    });
    
    const result = aiService.parseJSONResponse(response.content, {
      selectedTools: [],
      selectedConcepts: [],
      reasoning: ''
    });
    
    if (!Array.isArray(result.selectedTools) || !Array.isArray(result.selectedConcepts)) {
      throw new Error('AI selection returned invalid structure');
    }

    const totalSelected = result.selectedTools.length + result.selectedConcepts.length;
    if (totalSelected === 0) {
      throw new Error('AI selection returned empty selection');
    }
    
    const selectedTools = this.mapSelectedItems(result.selectedTools, candidateTools);
    const selectedConcepts = this.mapSelectedItems(result.selectedConcepts, candidateConcepts);
    
    const selectedMethods = selectedTools.filter((t: any) => t && t.type === 'method');
    const selectedSoftware = selectedTools.filter((t: any) => t && t.type === 'software');
    
    const confidence = this.calculateSelectionConfidence(result, candidateTools.length + candidateConcepts.length);
    
    const duration = Date.now() - startTime;
    
    logger.selection('ai-completed', selectedTools.length, selectedConcepts.length, {
      methods: selectedMethods.length,
      software: selectedSoftware.length,
      confidence: `${confidence}%`,
      duration: `${duration}ms`,
      selectionMethod
    });

    return {
      selectedTools,
      selectedConcepts,
      selectionMethod,
      confidence
    };
  }

  private prepareSelectionData(
    candidateMethods: any[],
    candidateSoftware: any[],
    candidateConcepts: any[],
    selectionMethod: string
  ): { toolsToSend: any[], conceptsToSend: any[] } {
    const isEmbeddingsBased = selectionMethod === 'embeddings_candidates';
    
    const totalLimit = isEmbeddingsBased ? 
      this.embeddingSelectionLimit : 
      this.noEmbeddingsToolLimit;
    
    const conceptLimit = isEmbeddingsBased ? 
      this.embeddingConceptsLimit : 
      this.noEmbeddingsConceptLimit;
    
    const methodLimit = Math.ceil(totalLimit * this.methodSelectionRatio);
    const softwareLimit = Math.floor(totalLimit * this.softwareSelectionRatio);
    
    const toolsToSend = [
      ...candidateMethods.slice(0, methodLimit).map(this.createToolData),
      ...candidateSoftware.slice(0, softwareLimit).map(this.createToolData)
    ];
    
    // Fill remaining capacity
    const remainingCapacity = totalLimit - toolsToSend.length;
    if (remainingCapacity > 0) {
      if (candidateMethods.length > methodLimit) {
        toolsToSend.push(
          ...candidateMethods
            .slice(methodLimit, methodLimit + remainingCapacity)
            .map(this.createToolData)
        );
      } else if (candidateSoftware.length > softwareLimit) {
        toolsToSend.push(
          ...candidateSoftware
            .slice(softwareLimit, softwareLimit + remainingCapacity)
            .map(this.createToolData)
        );
      }
    }
    
    const conceptsToSend = candidateConcepts
      .slice(0, conceptLimit)
      .map(this.createConceptData);
    
    logger.debug('tool-selector', 'Selection data prepared', {
      toolsToSend: toolsToSend.length,
      conceptsToSend: conceptsToSend.length,
      methodLimit,
      softwareLimit,
      conceptLimit,
      isEmbeddingsBased
    });
    
    return { toolsToSend, conceptsToSend };
  }

  private createToolData = (tool: any) => ({
    name: tool.name,
    type: tool.type,
    description: tool.description,
    domains: tool.domains,
    phases: tool.phases,
    platforms: tool.platforms || [],
    tags: tool.tags || [],
    skillLevel: tool.skillLevel,
    license: tool.license,
    accessType: tool.accessType,
    projectUrl: tool.projectUrl,
    knowledgebase: tool.knowledgebase,
    related_concepts: tool.related_concepts || [],
    related_software: tool.related_software || []
  });

  private createConceptData = (concept: any) => ({
    name: concept.name,
    type: 'concept',
    description: concept.description,
    domains: concept.domains,
    phases: concept.phases,
    tags: concept.tags || [],
    skillLevel: concept.skillLevel,
    related_concepts: concept.related_concepts || [],
    related_software: concept.related_software || []
  });

  private mapSelectedItems(selectedNames: string[], candidates: any[]): any[] {
    const candidatesMap = new Map(candidates.map((item: any) => [item.name, item]));
    
    return selectedNames
      .map((name: string) => candidatesMap.get(name))
      .filter((item: any): item is NonNullable<any> => item !== undefined && item !== null);
  }

  private calculateSelectionConfidence(result: any, candidateCount: number): number {
    if (!result?.selectedTools) return 30;
    
    const selectionRatio = result.selectedTools.length / candidateCount;
    const hasReasoning = result.reasoning && result.reasoning.length > 50;
    
    let confidence = 60;
    
    if (selectionRatio > 0.05 && selectionRatio < 0.3) confidence += 20;
    else if (selectionRatio <= 0.05) confidence -= 10;
    else confidence -= 15;
    
    if (hasReasoning) confidence += 15;
    if (result.selectedConcepts?.length > 0) confidence += 5;
    
    return Math.min(95, Math.max(25, confidence));
  }
}

export const toolSelector = new ToolSelector();