import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as fs from 'fs/promises';
import * as path from 'path';
import axios from 'axios';
import FormData from 'form-data';
import { LibreOfficeConvertResponse, LibreOfficeHealthResponse } from './libreoffice.interface';
import { I18nService } from '../i18n/i18n.service';

@Injectable()
export class LibreOfficeService implements OnModuleInit {
  private readonly logger = new Logger(LibreOfficeService.name);
  private baseUrl: string;

  constructor(
    private configService: ConfigService,
    private i18nService: I18nService,
  ) { }

  onModuleInit() {
    const libreofficeUrl = this.configService.get<string>('LIBREOFFICE_URL');
    if (!libreofficeUrl) {
      throw new Error(this.i18nService.getMessage('libreofficeUrlRequired'));
    }
    this.baseUrl = libreofficeUrl;
    this.logger.log(`LibreOffice service initialized with base URL: ${this.baseUrl}`);
  }

  /**
   * Check LibreOffice service health status
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await axios.get<LibreOfficeHealthResponse>(
        `${this.baseUrl}/health`,
        { timeout: 5000 }
      );
      return response.data.status === 'healthy';
    } catch (error) {
      this.logger.error('LibreOffice health check failed', error.message);
      return false;
    }
  }

  /**
   * Convert document to PDF
   * @param filePath Path of file to convert
   * @returns PDF file path
   */
  async convertToPDF(filePath: string): Promise<string> {
    const fileName = path.basename(filePath);
    const ext = path.extname(fileName).toLowerCase();

    // Return original path directly if PDF
    if (ext === '.pdf') {
      this.logger.log(`File is already PDF: ${filePath}`);
      return filePath;
    }

    // Check if file exists
    try {
      await fs.access(filePath);
    } catch {
      throw new Error(`File does not exist: ${filePath}`);
    }

    // Generate output PDF path
    const dir = path.dirname(filePath);
    const baseName = path.basename(filePath, ext);
    const targetPdfPath = path.join(dir, `${baseName}.pdf`);

    // Return directly if PDF already exists
    try {
      await fs.access(targetPdfPath);
      this.logger.log(`PDF already exists: ${targetPdfPath}`);
      return targetPdfPath;
    } catch {
      // Need to convert as PDF does not exist
    }

    // Load file
    const fileBuffer = await fs.readFile(filePath);

    // Build FormData
    const formData = new FormData();
    formData.append('file', fileBuffer, fileName);

    this.logger.log(`Converting ${fileName} to PDF...`);

    // Conversion retry count
    const maxRetries = 3;
    let lastError: any;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        // Call LibreOffice service
        const response = await axios.post(
          `${this.baseUrl}/convert`,
          formData,
          {
            headers: formData.getHeaders(),
            timeout: 300000, // 5 minute timeout
            responseType: 'stream', // Receive file stream
            maxRedirects: 5, // Max redirects
          }
        );

        // Write stream to output file
        const writer = (await import('fs')).createWriteStream(targetPdfPath);
        response.data.pipe(writer);

        return new Promise((resolve, reject) => {
          writer.on('finish', () => {
            this.logger.log(`Conversion completed: ${fileName} -> ${targetPdfPath}`);
            resolve(targetPdfPath);
          });
          writer.on('error', (err) => {
            this.logger.error(`Error writing PDF file: ${err.message}`);
            reject(err);
          });
        });

      } catch (error) {
        this.logger.error(`Attempt ${attempt} failed for ${fileName}: ${error.message}`);
        lastError = error;

        // Wait and retry on socket hang up or connection error
        if (error.code === 'ECONNRESET' || error.code === 'ECONNREFUSED' || error.code === 'ETIMEDOUT' || error.message.includes('socket hang up')) {
          if (attempt < maxRetries) {
            const delay = 2000 * attempt; // Increasing delay
            this.logger.log(`Waiting ${delay}ms before retry...`);
            await new Promise(resolve => setTimeout(resolve, delay));
          }
        } else {
          // Do not retry other errors
          break;
        }
      }
    }

    // Detailed error handling if all retries fail
    if (lastError.response) {
      try {
        const stream = lastError.response.data;
        const chunks: any[] = [];
        for await (const chunk of stream) {
          chunks.push(chunk);
        }
        const errorText = Buffer.concat(chunks).toString('utf8');
        this.logger.error(`LibreOffice service error details: ${errorText}`);

        let detail = errorText;
        try {
          const errorJson = JSON.parse(errorText);
          detail = errorJson.detail || errorText;
        } catch {
          // ignore JSON parse error
        }

        if (lastError.response.status === 504) {
          throw new Error('Conversion timed out. The file may be too large.');
        }
        throw new Error(`Conversion failed: ${detail}`);

      } catch (streamError) {
        this.logger.error('Error reading error stream:', streamError);
        throw new Error(`Conversion failed: ${lastError.message}`);
      }
    }

    this.logger.error(`Conversion failed for ${fileName} after ${maxRetries} attempts:`, lastError.message);
    if (lastError.code === 'ECONNREFUSED') {
      throw new Error('LibreOffice service is not running. Please check the service status.');
    }
    if (lastError.code === 'ECONNRESET' || lastError.message.includes('socket hang up')) {
      throw new Error('Connection to LibreOffice service was reset. The service may be unstable.');
    }
    throw new Error(`Conversion failed: ${lastError.message}`);
  }

  /**
   * Batch convert files
   */
  async batchConvert(filePaths: string[]): Promise<string[]> {
    const results: string[] = [];
    for (const filePath of filePaths) {
      try {
        const pdfPath = await this.convertToPDF(filePath);
        results.push(pdfPath);
      } catch (error) {
        this.logger.error(`Failed to convert ${filePath}: ${error.message}`);
        throw error;
      }
    }
    return results;
  }

  /**
   * Get service version information
   */
  async getVersion(): Promise<any> {
    try {
      const response = await axios.get(`${this.baseUrl}/version`);
      return response.data;
    } catch (error) {
      this.logger.error('Failed to get version', error.message);
      return null;
    }
  }
}
