import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModelConfigService } from '../model-config/model-config.service';

export interface EmbeddingResponse {
  data: Array<{
    embedding: number[];
    index: number;
  }>;
  model: string;
  usage: {
    prompt_tokens: number;
    total_tokens: number;
  };
}

@Injectable()
export class EmbeddingService {
  private readonly logger = new Logger(EmbeddingService.name);
  private readonly defaultDimensions: number;

  constructor(
    private modelConfigService: ModelConfigService,
    private configService: ConfigService,
  ) {
    this.defaultDimensions = parseInt(
      this.configService.get<string>('DEFAULT_VECTOR_DIMENSIONS', '2560'),
    );
    this.logger.log(`Default vector dimensions set to ${this.defaultDimensions}`);
  }

  async getEmbeddings(
    texts: string[],
    userId: string,
    embeddingModelConfigId: string,
    tenantId?: string,
  ): Promise<number[][]> {
    this.logger.log(`Generating embedding vectors for ${texts.length} texts`);

    const modelConfig = await this.modelConfigService.findOne(
      embeddingModelConfigId,
      userId,
      tenantId || 'default',
    );
    if (!modelConfig || modelConfig.type !== 'embedding') {
      throw new Error(`Embedded model configuration ${embeddingModelConfigId} not found`);
    }

    if (modelConfig.isEnabled === false) {
      throw new Error(`Unable to generate embedding vector because model ${modelConfig.name} is disabled`);
    }

    // API key is optional - allow local models

    if (!modelConfig.baseUrl) {
      throw new Error(`baseUrl not set for model ${modelConfig.name}`);
    }

    
    const maxBatchSize = this.getMaxBatchSizeForModel(modelConfig.modelId, modelConfig.maxBatchSize);

    
    if (texts.length > maxBatchSize) {
      this.logger.log(
        `Text count ${texts.length} exceeds model batch limit ${maxBatchSize}, splitting into batches`
      );

      const allEmbeddings: number[][] = [];

      for (let i = 0; i < texts.length; i += maxBatchSize) {
        const batch = texts.slice(i, i + maxBatchSize);
        const batchEmbeddings = await this.getEmbeddingsForBatch(
          batch,
          userId,
          modelConfig,
          maxBatchSize
        );

        allEmbeddings.push(...batchEmbeddings);

        
        if (i + maxBatchSize < texts.length) {
          await new Promise(resolve => setTimeout(resolve, 100)); 
        }
      }

      return allEmbeddings;
    } else {
      
      return await this.getEmbeddingsForBatch(
        texts,
        userId,
        modelConfig,
        maxBatchSize
      );
    }
  }

  
  private getMaxBatchSizeForModel(modelId: string, configuredMaxBatchSize?: number): number {
    
    if (modelId.includes('text-embedding-004') || modelId.includes('text-embedding-v4') ||
      modelId.includes('text-embedding-ada-002')) {
      return Math.min(10, configuredMaxBatchSize || 100); 
    } else if (modelId.includes('text-embedding-3') || modelId.includes('text-embedding-003')) {
      return Math.min(2048, configuredMaxBatchSize || 2048); 
    } else {
      
      return Math.min(configuredMaxBatchSize || 100, 100);
    }
  }

  
  private async getEmbeddingsForBatch(
    texts: string[],
    userId: string,
    modelConfig: any,
    maxBatchSize: number,
  ): Promise<number[][]> {
    const apiUrl = modelConfig.baseUrl.endsWith('/embeddings')
      ? modelConfig.baseUrl
      : `${modelConfig.baseUrl}/embeddings`;

    let lastError;
    const MAX_RETRIES = 3;

    for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
      try {
        const controller = new AbortController();
        const timeoutId = setTimeout(() => {
          controller.abort();
          this.logger.error(`Embedding API timeout after 60s: ${apiUrl}`);
        }, 60000); // 60s timeout

        this.logger.log(`[Model Call] Type: Embedding, Model: ${modelConfig.name} (${modelConfig.modelId}), User: ${userId}, Text Count: ${texts.length}`);
        this.logger.log(`Calling Embedding API (Attempt ${attempt}/${MAX_RETRIES}): ${apiUrl}`);

        let response;
        try {
          response = await fetch(apiUrl, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              Authorization: `Bearer ${modelConfig.apiKey}`,
            },
            body: JSON.stringify({
              encoding_format: 'float',
              input: texts,
              model: modelConfig.modelId,
            }),
            signal: controller.signal,
          });
        } finally {
          clearTimeout(timeoutId);
        }

        if (!response.ok) {
          const errorText = await response.text();

          
          if (errorText.includes('batch size is invalid') || errorText.includes('batch_size') ||
            errorText.includes('invalid') || errorText.includes('larger than')) {
            this.logger.warn(
              `Batch size limit error detected. Halving batch size and retrying: ${maxBatchSize} -> ${Math.floor(maxBatchSize / 2)}`
            );

            
            if (texts.length > 1) {
              const midPoint = Math.floor(texts.length / 2);
              const firstHalf = texts.slice(0, midPoint);
              const secondHalf = texts.slice(midPoint);

              const firstResult = await this.getEmbeddingsForBatch(firstHalf, userId, modelConfig, Math.floor(maxBatchSize / 2));
              const secondResult = await this.getEmbeddingsForBatch(secondHalf, userId, modelConfig, Math.floor(maxBatchSize / 2));

              return [...firstResult, ...secondResult];
            }
          }

          
          if (errorText.includes('context length') || errorText.includes('exceeds')) {
            const avgLength = texts.reduce((s, t) => s + t.length, 0) / texts.length;
            const totalLength = texts.reduce((s, t) => s + t.length, 0);
            this.logger.error(
              `Text length exceeded limit: Input ${texts.length} texts,` +
              `Total ${totalLength} characters, average ${Math.round(avgLength)} characters, ` +
              `Model limit: ${modelConfig.maxInputTokens || 8192} tokens`
            );
            throw new Error(
              `Text length is a limitation of the model. ` +
              `Currently: ${texts.length} texts totaling ${totalLength} characters, ` +
              `Model limit: ${modelConfig.maxInputTokens || 8192} tokens. ` +
              `Advice: Reduce chunk size or batch size`
            );
          }

          
          if (response.status === 429 || response.status >= 500) {
            this.logger.warn(`Temporary error occurred in Embedding API (${response.status}): ${errorText}`);
            throw new Error(`API Error ${response.status}: ${errorText}`);
          }

          this.logger.error(`Embedding API Error Details: ${errorText}`);
          this.logger.error(`Request parameters: model=${modelConfig.modelId}, inputLength=${texts[0]?.length}`);
          throw new Error(`Embedded API call failed: ${response.statusText} - ${errorText}`);
        }

        const data: EmbeddingResponse = await response.json();
        const embeddings = data.data.map((item) => item.embedding);

        
        const actualDimensions = embeddings[0]?.length || this.defaultDimensions;
        this.logger.log(
          `Retrieved ${embeddings.length} embeddings from ${modelConfig.name}. Dimensions: ${actualDimensions}`,
        );

        return embeddings;
      } catch (error) {
        lastError = error;

        
        if (attempt < MAX_RETRIES) {
          const delay = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
          this.logger.warn(`Embedding request failed. Retrying in ${delay}ms: ${error.message}`);
          await new Promise(resolve => setTimeout(resolve, delay));
          continue;
        }
      }
    }

    throw lastError;
  }

  private getEstimatedDimensions(modelId: string): number {
    
    return this.defaultDimensions;
  }
}
