import { Injectable, Inject, Logger, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ExportReportCommand } from './export-report.command';
import {
  IReportSnapshotRepository,
  IReportFileRepository,
  REPORT_SNAPSHOT_REPOSITORY,
  REPORT_FILE_REPOSITORY,
} from '../../../domain/repositories';
import { ReportFile } from '../../../domain/entities/report-file.entity';
import {
  OutputFormat,
  OutputFormatMimeTypes,
  OutputFormatExtensions,
} from '../../../domain/value-objects';
import { ExcelExportService } from '../../../infrastructure/export/excel-export.service';
import { CsvExportService } from '../../../infrastructure/export/csv-export.service';
import { PdfExportService } from '../../../infrastructure/export/pdf-export.service';

@Injectable()
export class ExportReportHandler {
  private readonly logger = new Logger(ExportReportHandler.name);
  private readonly fileUrlPrefix: string;

  constructor(
    @Inject(REPORT_SNAPSHOT_REPOSITORY)
    private readonly snapshotRepo: IReportSnapshotRepository,
    @Inject(REPORT_FILE_REPOSITORY)
    private readonly fileRepo: IReportFileRepository,
    private readonly excelExportService: ExcelExportService,
    private readonly csvExportService: CsvExportService,
    private readonly pdfExportService: PdfExportService,
    private readonly configService: ConfigService,
  ) {
    this.fileUrlPrefix = this.configService.get<string>(
      'FILE_STORAGE_URL_PREFIX',
      'http://localhost:3008/files',
    );
  }

  async execute(command: ExportReportCommand): Promise<ReportFile> {
    this.logger.log(
      `Exporting report snapshot ${command.snapshotId} to ${command.format}`,
    );

    // Find snapshot
    const snapshot = await this.snapshotRepo.findById(command.snapshotId);
    if (!snapshot) {
      throw new NotFoundException(
        `Report snapshot not found: ${command.snapshotId}`,
      );
    }

    // Check if file already exists
    const existingFile = await this.fileRepo.findBySnapshotIdAndFormat(
      command.snapshotId,
      command.format,
    );

    if (existingFile && !existingFile.isExpired()) {
      this.logger.debug(`Using existing file for snapshot ${command.snapshotId}`);
      return existingFile;
    }

    // Generate file name
    const extension = OutputFormatExtensions[command.format];
    const fileName = `${snapshot.reportCode}_${snapshot.periodKey}_${Date.now()}.${extension}`;

    // Get columns from snapshot data
    const rows = snapshot.snapshotData.rows;
    const columns = this.inferColumns(rows);

    // Export based on format
    let exportResult: { filePath: string; fileSize: number };

    switch (command.format) {
      case OutputFormat.EXCEL:
        exportResult = await this.excelExportService.export(fileName, {
          sheetName: snapshot.reportCode,
          columns,
          data: rows,
          title: `${snapshot.reportCode} Report`,
          subtitle: `Period: ${snapshot.periodKey}`,
        });
        break;

      case OutputFormat.CSV:
        exportResult = await this.csvExportService.export(fileName, {
          columns,
          data: rows,
        });
        break;

      case OutputFormat.PDF:
        exportResult = await this.pdfExportService.export(fileName, {
          columns,
          data: rows,
          title: `${snapshot.reportCode} Report`,
          subtitle: `Period: ${snapshot.periodKey}`,
        });
        break;

      case OutputFormat.JSON:
        exportResult = await this.exportJson(fileName, rows);
        break;

      default:
        throw new Error(`Unsupported format: ${command.format}`);
    }

    // Create report file entity
    const reportFile = ReportFile.create({
      snapshotId: command.snapshotId,
      fileName,
      filePath: exportResult.filePath,
      fileUrl: `${this.fileUrlPrefix}/${fileName}`,
      fileSize: BigInt(exportResult.fileSize),
      fileFormat: command.format,
      mimeType: OutputFormatMimeTypes[command.format],
    });

    // Save file record
    const savedFile = await this.fileRepo.save(reportFile);

    this.logger.log(
      `Report exported: ${fileName} (${exportResult.fileSize} bytes)`,
    );

    return savedFile;
  }

  private inferColumns(
    rows: any[],
  ): { header: string; key: string; width?: number }[] {
    if (rows.length === 0) {
      return [];
    }

    const firstRow = rows[0];
    return Object.keys(firstRow).map((key) => ({
      header: this.formatHeader(key),
      key,
      width: 15,
    }));
  }

  private formatHeader(key: string): string {
    return key
      .replace(/([A-Z])/g, ' $1')
      .replace(/^./, (str) => str.toUpperCase())
      .trim();
  }

  private async exportJson(
    fileName: string,
    data: any[],
  ): Promise<{ filePath: string; fileSize: number }> {
    const fs = await import('fs');
    const path = await import('path');
    const storagePath = this.configService.get<string>(
      'FILE_STORAGE_PATH',
      './storage/reports',
    );

    const fullPath = path.join(storagePath, fileName);
    const dir = path.dirname(fullPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }

    const content = JSON.stringify(data, null, 2);
    fs.writeFileSync(fullPath, content, 'utf8');

    const stats = fs.statSync(fullPath);

    return {
      filePath: fullPath,
      fileSize: stats.size,
    };
  }
}
