import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs/promises';
import * as path from 'path';
import { LibreOfficeService } from '../libreoffice/libreoffice.service';
import { Pdf2ImageService } from '../pdf2image/pdf2image.service';
import { VisionService } from '../vision/vision.service';
import { ModelConfigService } from '../model-config/model-config.service';
import {
  ModeRecommendation,
  PipelineResult,
  PreciseModeOptions,
  ProcessingStatus,
} from './vision-pipeline.interface';
import {
  VisionAnalysisResult,
  VisionModelConfig,
} from '../vision/vision.interface';
import { I18nService } from '../i18n/i18n.service';

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

  constructor(
    private libreOffice: LibreOfficeService,
    private pdf2Image: Pdf2ImageService,
    private vision: VisionService,
    private modelConfigService: ModelConfigService,
    private configService: ConfigService,
    private i18nService: I18nService,
  ) { }

  /**
   * Main processing flow: Precise mode
   * Returns the processing result, and the caller is responsible for vectorization and indexing.
   */
  async processPreciseMode(
    filePath: string,
    options: PreciseModeOptions,
  ): Promise<PipelineResult> {
    const startTime = Date.now();
    const results: VisionAnalysisResult[] = [];
    let processedPages = 0;
    let failedPages = 0;
    let totalCost = 0;
    let pdfPath = filePath;
    let imagesToProcess: any[] = [];

    this.logger.log(
      `🚀 Starting precise mode processing: ${options.fileName} (User: ${options.userId})`,
    );

    try {
      // Step 1: Unification of formats
      this.logger.log('📄 Step 1/4: Unification of formats');
      this.updateStatus('converting', 10, 'Converting document format...');
      try {
        pdfPath = await this.convertToPDF(filePath);
        this.logger.log(`✅ Format conversion completed: ${pdfPath}`);
      } catch (convertError) {
        this.logger.error(`❌ Format conversion failed: ${convertError.message}`);
        throw convertError;
      }

      // Step 2: Conversion from PDF to images
      this.logger.log('🖼️  Step 2/4: Conversion from PDF to images');
      this.updateStatus('splitting', 30, 'Converting PDF to images...');
      let conversionResult;
      try {
        conversionResult = await this.pdf2Image.convertToImages(pdfPath, {
          density: 300,
          quality: 85,
          format: 'jpeg',
        });
      } catch (imageError) {
        this.logger.error(`❌ PDF to image conversion failed: ${imageError.message}`);
        throw imageError;
      }

      if (conversionResult.images.length === 0) {
        throw new Error(this.i18nService.getMessage('pdfToImageConversionFailed'));
      }

      this.logger.log(
        `✅ PDF to image conversion completed: Total ${conversionResult.totalPages} pages, ${conversionResult.images.length} images generated`,
      );

      // Limit the number of pages to process
      imagesToProcess = options.maxPages
        ? conversionResult.images.slice(0, options.maxPages)
        : conversionResult.images;

      this.logger.log(
        `📊 Processing ${imagesToProcess.length} pages (${options.maxPages ? 'limited' : 'all'})`,
      );

      // Step 3: Get Vision model configuration
      this.logger.log('🤖 Step 3/4: Preparation of Vision model');
      const modelConfig = await this.getVisionModelConfig(
        options.userId,
        options.modelId,
        options.tenantId,
      );
      this.logger.log(`✅ Vision model configuration completed: ${modelConfig.modelId}`);

      // Step 4: VL model analysis
      this.logger.log('🔍 Step 4/4: Vision model analysis');
      this.updateStatus('analyzing', 50, 'Analyzing pages using Vision model...');

      // Display processing progress of each page
      this.logger.log(`Starting analysis of ${imagesToProcess.length} page contents...`);
      const batchResult = await this.vision.batchAnalyze(
        imagesToProcess.map((img) => img.path),
        modelConfig,
        {
          startIndex: 1,
          skipQualityCheck: options.skipQualityCheck,
          onProgress: (current: number, total: number, pageResult?: any) => {
            const progress = Math.round((current / total) * 100);
            this.logger.log(
              `📄 Processing progress: ${current}/${total} (${progress}%) ${pageResult ? `- Page ${pageResult.pageIndex} completed` : ''}`,
            );
          },
        },
      );

      totalCost = batchResult.estimatedCost;
      processedPages = batchResult.successCount;
      failedPages = batchResult.failedCount;
      results.push(...batchResult.results);

      this.logger.log(
        `✅ Vision analysis completed: Success ${processedPages} pages, Fail ${failedPages} pages, Cost $${totalCost.toFixed(2)}`,
      );

      // Step 5: Cleanup of temporary files (images)
      this.logger.log('🧹 Cleaning up temporary files...');
      this.updateStatus('completed', 100, 'Processing completed. Cleaning up temporary files...');
      await this.pdf2Image.cleanupImages(imagesToProcess);

      // If converted to PDF, clean up the converted file
      if (pdfPath !== filePath) {
        try {
          await fs.unlink(pdfPath);
          this.logger.log('🗑️  Cleaned up converted PDF file');
        } catch (error) {
          this.logger.warn(`⚠️  Failed to clean up converted PDF: ${error.message}`);
        }
      }

      const duration = (Date.now() - startTime) / 1000;

      this.logger.log(
        `🎉 Precise mode processing completed! ` +
        `📊 Statistics: ${processedPages}/${imagesToProcess.length} pages success, ` +
        `💰 Cost: $${totalCost.toFixed(2)}, ` +
        `⏱️  Duration: ${duration.toFixed(1)}s`,
      );

      return {
        success: true,
        fileId: options.fileId,
        fileName: options.fileName,
        totalPages: conversionResult.totalPages,
        processedPages,
        failedPages,
        results,
        cost: totalCost,
        duration,
        mode: 'precise',
      };
    } catch (error) {
      this.logger.error(`❌ Precise mode processing failed: ${error.message}`);

      // Attempting cleanup of temporary files
      try {
        if (pdfPath !== filePath && pdfPath !== filePath) {
          await fs.unlink(pdfPath);
        }
        if (imagesToProcess.length > 0) {
          await this.pdf2Image.cleanupImages(imagesToProcess);
        }
        this.logger.log('🧹 Cleaned up temporary files');
      } catch {
        this.logger.warn('⚠️  Failed to clean up temporary files');
      }

      return {
        success: false,
        fileId: options.fileId,
        fileName: options.fileName,
        totalPages: 0,
        processedPages,
        failedPages,
        results: [],
        cost: totalCost,
        duration: (Date.now() - startTime) / 1000,
        mode: 'precise',
      };
    }
  }

  /**
   * Get Vision model configuration
   */
  private async getVisionModelConfig(
    userId: string,
    modelId: string,
    tenantId?: string,
  ): Promise<VisionModelConfig> {
    const config = await this.modelConfigService.findOne(modelId);

    if (!config) {
      throw new Error(`Model configuration not found: ${modelId}`);
    }

    // API key is optional - Allows local models

    return {
      baseUrl: config.baseUrl || '',
      apiKey: config.apiKey || '',
      modelId: config.modelId,
    };
  }

  /**
   * Conversion to PDF
   */
  private async convertToPDF(filePath: string): Promise<string> {
    const ext = path.extname(filePath).toLowerCase();

    // If already PDF, return as is
    if (ext === '.pdf') {
      return filePath;
    }

    // Call LibreOffice to convert
    const containerPdfPath = await this.libreOffice.convertToPDF(filePath);

    // The path returned from the LibreOffice container is already correct. All point to the same uploads directory.
    // Inside container: /uploads/xxx.pdf -> Host machine: ../uploads/xxx.pdf
    const hostPdfPath = containerPdfPath.startsWith('/uploads/')
      ? path.join('..', containerPdfPath) // ../uploads/xxx.pdf
      : containerPdfPath;

    this.logger.log(`Path conversion: ${containerPdfPath} -> ${hostPdfPath}`);

    // Check existence of file
    try {
      await fs.access(hostPdfPath);
      return hostPdfPath;
    } catch (error) {
      this.logger.error(`PDF file does not exist: ${hostPdfPath}`);
      throw new Error(`PDF file does not exist: ${hostPdfPath}`);
    }
  }

  /**
   * Index results to Elasticsearch (using knowledge-base embedding service)
   * Note: This method requires the embedding service, so it should be called within knowledge-base.service.
   */
  private async indexResults(
    results: any[],
    options: PreciseModeOptions,
  ): Promise<void> {
    // This method is currently called from knowledge-base.service
    // vision-pipeline is only responsible for processing and returning results.
    this.logger.log(
      `indexResults called with ${results.length} results - should be handled by knowledge-base service`,
    );
    throw new Error(
      'VisionPipelineService.indexResults should not be called directly. Use knowledge-base service instead.',
    );
  }

  /**
   * Format detection and mode recommendation
   */
  async recommendMode(filePath: string): Promise<ModeRecommendation> {
    const ext = path.extname(filePath).toLowerCase();
    const stats = await fs.stat(filePath);
    const sizeMB = stats.size / (1024 * 1024);

    const supportedFormats = [
      '.pdf',
      '.doc',
      '.docx',
      '.ppt',
      '.pptx',
      '.xls',
      '.xlsx',
    ];
    const preciseFormats = ['.pdf', '.doc', '.docx', '.ppt', '.pptx'];

    if (!supportedFormats.includes(ext)) {
      return {
        recommendedMode: 'fast',
        reason: `Unsupported file format: ${ext}`,
        warnings: ['Using fast mode (text extraction only)'],
      };
    }

    if (!preciseFormats.includes(ext)) {
      return {
        recommendedMode: 'fast',
        reason: `Format ${ext} does not support precise mode`,
        warnings: ['Using fast mode (text extraction only)'],
      };
    }

    // File size check
    if (sizeMB > 50) {
      return {
        recommendedMode: 'precise',
        reason: 'The file is large, so precise mode is recommended to retain full information.',
        estimatedCost: sizeMB * 0.01, // Rough estimate
        estimatedTime: sizeMB * 12, // Approx. 12 seconds per 1MB
        warnings: ['Processing time may be long', 'API costs will occur'],
      };
    }

    // Precise mode recommended
    return {
      recommendedMode: 'precise',
      reason: 'Precise mode is available. Can retain mixed content of text and images.',
      estimatedCost: sizeMB * 0.01,
      estimatedTime: sizeMB * 10,
      warnings: ['API costs will occur'],
    };
  }

  /**
   * Update processing status (for real-time feedback)
   */
  private updateStatus(
    status: ProcessingStatus['status'],
    progress: number,
    message: string,
  ): void {
    // You can send WebSocket messages or update the database here.
    this.logger.log(`[${status}] ${progress}% - ${message}`);
  }
}
