import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModelConfigService } from '../model-config/model-config.service';
import { TenantService } from '../tenant/tenant.service';
import { UserSettingService } from '../user-setting/user-setting.service';


import {
  DEFAULT_CHUNK_SIZE,
  MIN_CHUNK_SIZE,
  DEFAULT_CHUNK_OVERLAP,
  MIN_CHUNK_OVERLAP,
  DEFAULT_MAX_OVERLAP_RATIO,
  DEFAULT_MAX_BATCH_SIZE,
  DEFAULT_VECTOR_DIMENSIONS
} from '../common/constants';
import { I18nService } from '../i18n/i18n.service';

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


  private readonly DEFAULTS = {
    chunkSize: DEFAULT_CHUNK_SIZE,
    chunkOverlap: DEFAULT_CHUNK_OVERLAP,
    minChunkSize: MIN_CHUNK_SIZE,
    minChunkOverlap: MIN_CHUNK_OVERLAP,
    maxOverlapRatio: DEFAULT_MAX_OVERLAP_RATIO,
    maxBatchSize: DEFAULT_MAX_BATCH_SIZE,
    expectedDimensions: DEFAULT_VECTOR_DIMENSIONS,
  };


  private readonly envMaxChunkSize: number;
  private readonly envMaxOverlapSize: number;

  constructor(
    private configService: ConfigService,
    private modelConfigService: ModelConfigService,
    private i18nService: I18nService,
    private tenantService: TenantService,
    private userSettingService: UserSettingService,
  ) {

    this.envMaxChunkSize = parseInt(
      this.configService.get<string>('MAX_CHUNK_SIZE', '8191')
    );
    this.envMaxOverlapSize = parseInt(
      this.configService.get<string>('MAX_OVERLAP_SIZE', '2000')
    );

    this.logger.log(
      `Environment variable limits: MAX_CHUNK_SIZE=${this.envMaxChunkSize}, MAX_OVERLAP_SIZE=${this.envMaxOverlapSize}`
    );
  }


  async getModelLimits(modelId: string, userId: string, tenantId?: string): Promise<{
    maxInputTokens: number;
    maxBatchSize: number;
    expectedDimensions: number;
    providerName: string;
    isVectorModel: boolean;
  }> {
    const modelConfig = await this.modelConfigService.findOne(modelId, userId, tenantId || '');

    if (!modelConfig || modelConfig.type !== 'embedding') {
      throw new BadRequestException(this.i18nService.formatMessage('embeddingModelNotFound', { id: modelId }));
    }


    const maxInputTokens = modelConfig.maxInputTokens || this.envMaxChunkSize;
    const maxBatchSize = modelConfig.maxBatchSize || this.DEFAULTS.maxBatchSize;
    const expectedDimensions = modelConfig.dimensions || parseInt(this.configService.get('DEFAULT_VECTOR_DIMENSIONS', String(this.DEFAULTS.expectedDimensions)));
    const providerName = modelConfig.providerName || 'Unknown';
    const isVectorModel = modelConfig.isVectorModel || false;

    this.logger.log(
      this.i18nService.formatMessage('configLoaded', { name: modelConfig.name, id: modelConfig.modelId }) + '\n' +
      `  - Provider: ${providerName}\n` +
      `  - Token Limit: ${maxInputTokens}\n` +
      `  - Batch Limit: ${maxBatchSize}\n` +
      `  - Vector Dimensions: ${expectedDimensions}\n` +
      `  - Is Vector Model: ${isVectorModel}`,
    );

    return {
      maxInputTokens,
      maxBatchSize,
      expectedDimensions,
      providerName,
      isVectorModel,
    };
  }


  async validateChunkConfig(
    chunkSize: number,
    chunkOverlap: number,
    modelId: string,
    userId: string,
    tenantId?: string,
  ): Promise<{
    chunkSize: number;
    chunkOverlap: number;
    warnings: string[];
    effectiveMaxChunkSize: number;
    effectiveMaxOverlapSize: number;
  }> {
    const warnings: string[] = [];
    const limits = await this.getModelLimits(modelId, userId, tenantId);


    const effectiveMaxChunkSize = Math.min(
      this.envMaxChunkSize,
      limits.maxInputTokens,
    );

    const effectiveMaxOverlapSize = Math.min(
      this.envMaxOverlapSize,
      Math.floor(effectiveMaxChunkSize * this.DEFAULTS.maxOverlapRatio),
    );


    if (chunkSize > effectiveMaxChunkSize) {
      const reason =
        this.envMaxChunkSize < limits.maxInputTokens
          ? `${this.i18nService.getMessage('environmentLimit')} ${this.envMaxChunkSize}`
          : `${this.i18nService.getMessage('modelLimit')} ${limits.maxInputTokens}`;

      warnings.push(
        this.i18nService.formatMessage('chunkOverflow', {
          size: chunkSize,
          max: effectiveMaxChunkSize,
          reason
        })
      );
      chunkSize = effectiveMaxChunkSize;
    }


    if (chunkSize < this.DEFAULTS.minChunkSize) {
      warnings.push(
        this.i18nService.formatMessage('chunkUnderflow', {
          size: chunkSize,
          min: this.DEFAULTS.minChunkSize
        })
      );
      chunkSize = this.DEFAULTS.minChunkSize;
    }


    if (chunkOverlap > effectiveMaxOverlapSize) {
      warnings.push(
        this.i18nService.formatMessage('overlapOverflow', {
          size: chunkOverlap,
          max: effectiveMaxOverlapSize
        })
      );
      chunkOverlap = effectiveMaxOverlapSize;
    }


    const maxOverlapByRatio = Math.floor(
      chunkSize * this.DEFAULTS.maxOverlapRatio,
    );
    if (chunkOverlap > maxOverlapByRatio) {
      warnings.push(
        this.i18nService.formatMessage('overlapRatioExceeded', {
          size: chunkOverlap,
          max: maxOverlapByRatio
        })
      );
      chunkOverlap = maxOverlapByRatio;
    }

    if (chunkOverlap < this.DEFAULTS.minChunkOverlap) {
      warnings.push(
        this.i18nService.formatMessage('overlapUnderflow', {
          size: chunkOverlap,
          min: this.DEFAULTS.minChunkOverlap
        })
      );
      chunkOverlap = this.DEFAULTS.minChunkOverlap;
    }



    const safetyMargin = 0.8;
    const safeChunkSize = Math.floor(effectiveMaxChunkSize * safetyMargin);

    if (chunkSize > safeChunkSize) {
      warnings.push(
        this.i18nService.formatMessage('batchOverflowWarning', {
          safeSize: safeChunkSize,
          size: chunkSize,
          percent: Math.round(safetyMargin * 100)
        })
      );
    }


    const estimatedChunkCount = this.estimateChunkCount(
      1000000,
      chunkSize,
    );

    if (estimatedChunkCount > 50000) {
      warnings.push(
        this.i18nService.formatMessage('estimatedChunkCountExcessive', { count: estimatedChunkCount })
      );
    }

    return {
      chunkSize,
      chunkOverlap,
      warnings,
      effectiveMaxChunkSize,
      effectiveMaxOverlapSize,
    };
  }


  async getRecommendedBatchSize(
    modelId: string,
    userId: string,
    tenantId?: string,
    currentBatchSize: number = 100,
  ): Promise<number> {
    const limits = await this.getModelLimits(modelId, userId, tenantId);


    const recommended = Math.min(
      currentBatchSize,
      limits.maxBatchSize,
      200,
    );

    if (recommended < currentBatchSize) {
      this.logger.warn(
        this.i18nService.formatMessage('batchSizeAdjusted', {
          old: currentBatchSize,
          new: recommended,
          limit: limits.maxBatchSize
        })
      );
    }

    return Math.max(10, recommended);
  }


  estimateChunkCount(textLength: number, chunkSize: number): number {
    const chunkSizeInChars = chunkSize * 4; // 1 token ≈ 4 chars
    return Math.ceil(textLength / chunkSizeInChars);
  }


  async validateDimensions(
    modelId: string,
    userId: string,
    actualDimensions: number,
    tenantId?: string,
  ): Promise<boolean> {
    const limits = await this.getModelLimits(modelId, userId, tenantId);

    if (actualDimensions !== limits.expectedDimensions) {
      this.logger.warn(
        this.i18nService.formatMessage('dimensionMismatch', {
          id: modelId,
          expected: limits.expectedDimensions,
          actual: actualDimensions
        })
      );
      return false;
    }

    return true;
  }


  async getConfigSummary(
    chunkSize: number,
    chunkOverlap: number,
    modelId: string,
    userId: string,
    tenantId?: string,
  ): Promise<string> {
    const limits = await this.getModelLimits(modelId, userId, tenantId);

    return [
      `Model: ${modelId}`,
      `Chunk size: ${chunkSize} tokens (Limit: ${limits.maxInputTokens})`,
      `Overlap size: ${chunkOverlap} tokens`,
      `Batch size: ${limits.maxBatchSize}`,
      `Vector Dimensions: ${limits.expectedDimensions}`,
    ].join(', ');
  }


  async getFrontendLimits(
    modelId: string,
    userId: string,
    tenantId?: string,
  ): Promise<{
    maxChunkSize: number;
    maxOverlapSize: number;
    minOverlapSize: number;
    defaultChunkSize: number;
    defaultOverlapSize: number;
    modelInfo: {
      name: string;
      maxInputTokens: number;
      maxBatchSize: number;
      expectedDimensions: number;
    };
  }> {
    const limits = await this.getModelLimits(modelId, userId, tenantId);


    const maxChunkSize = Math.min(this.envMaxChunkSize, limits.maxInputTokens);
    const maxOverlapSize = Math.min(
      this.envMaxOverlapSize,
      Math.floor(maxChunkSize * this.DEFAULTS.maxOverlapRatio),
    );


    const modelConfig = await this.modelConfigService.findOne(modelId, userId, tenantId || '');
    const modelName = modelConfig?.name || 'Unknown';


    let defaultChunkSize = this.DEFAULTS.chunkSize;
    let defaultOverlapSize = this.DEFAULTS.chunkOverlap;

    if (tenantId) {
      const tenantSettings = await this.tenantService.getSettings(tenantId);
      if (tenantSettings.chunkSize) defaultChunkSize = tenantSettings.chunkSize;
      if (tenantSettings.chunkOverlap) defaultOverlapSize = tenantSettings.chunkOverlap;
    } else {
      const userSettings = await this.userSettingService.findOrCreate(userId);
      if (userSettings.chunkSize) defaultChunkSize = userSettings.chunkSize;
      if (userSettings.chunkOverlap) defaultOverlapSize = userSettings.chunkOverlap;
    }

    return {
      maxChunkSize,
      maxOverlapSize,
      minOverlapSize: this.DEFAULTS.minChunkOverlap,
      defaultChunkSize: Math.min(defaultChunkSize, maxChunkSize),
      defaultOverlapSize: Math.max(this.DEFAULTS.minChunkOverlap, Math.min(defaultOverlapSize, maxOverlapSize)),
      modelInfo: {
        name: modelName,
        maxInputTokens: limits.maxInputTokens,
        maxBatchSize: limits.maxBatchSize,
        expectedDimensions: limits.expectedDimensions,
      },
    };
  }
}
