/**
 * @ai-summary Service for creating user data export archives
 * @ai-context Creates tar.gz archives containing all user data and files
 */

import * as fsp from 'fs/promises';
import * as fs from 'fs';
import * as path from 'path';
import * as tar from 'tar';
import { logger } from '../../../core/logging/logger';
import { VehiclesRepository } from '../../vehicles/data/vehicles.repository';
import { FuelLogsRepository } from '../../fuel-logs/data/fuel-logs.repository';
import { DocumentsRepository } from '../../documents/data/documents.repository';
import { MaintenanceRepository } from '../../maintenance/data/maintenance.repository';
import { USER_EXPORT_CONFIG, UserExportManifest, UserExportResult } from './user-export.types';

export class UserExportArchiveService {
  private readonly tempPath: string;
  private readonly dataPath: string;

  constructor(
    private readonly vehiclesRepo: VehiclesRepository,
    private readonly fuelLogsRepo: FuelLogsRepository,
    private readonly documentsRepo: DocumentsRepository,
    private readonly maintenanceRepo: MaintenanceRepository
  ) {
    this.tempPath = USER_EXPORT_CONFIG.tempPath;
    this.dataPath = '/app/data/documents';
  }

  /**
   * Creates a complete user data export archive
   */
  async createUserExport(userId: string): Promise<UserExportResult> {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const workDir = path.join(this.tempPath, `export-${userId}-${Date.now()}`);

    try {
      // Create working directory structure
      await fsp.mkdir(workDir, { recursive: true });
      await fsp.mkdir(path.join(workDir, 'data'), { recursive: true });
      await fsp.mkdir(path.join(workDir, 'files'), { recursive: true });

      logger.info('Starting user data export', { userId, workDir });

      // Gather user data
      const [vehicles, fuelLogs, documents, maintenanceRecords] = await Promise.all([
        this.vehiclesRepo.findByUserId(userId),
        this.fuelLogsRepo.findByUserId(userId),
        this.documentsRepo.listByUser(userId),
        this.maintenanceRepo.findRecordsByUserId(userId),
      ]);

      const maintenanceSchedules = await this.maintenanceRepo.findSchedulesByUserId(userId);

      // Write JSON data files
      await Promise.all([
        fsp.writeFile(
          path.join(workDir, 'data', 'vehicles.json'),
          JSON.stringify(vehicles, null, 2)
        ),
        fsp.writeFile(
          path.join(workDir, 'data', 'fuel-logs.json'),
          JSON.stringify(fuelLogs, null, 2)
        ),
        fsp.writeFile(
          path.join(workDir, 'data', 'documents.json'),
          JSON.stringify(documents, null, 2)
        ),
        fsp.writeFile(
          path.join(workDir, 'data', 'maintenance-records.json'),
          JSON.stringify(maintenanceRecords, null, 2)
        ),
        fsp.writeFile(
          path.join(workDir, 'data', 'maintenance-schedules.json'),
          JSON.stringify(maintenanceSchedules, null, 2)
        ),
      ]);

      // Copy vehicle images
      const vehicleImageStats = await this.copyVehicleImages(workDir, vehicles);

      // Copy document files
      const documentFileStats = await this.copyDocumentFiles(workDir, documents);

      // Create manifest
      const warnings: string[] = [];
      if (vehicleImageStats.missingCount > 0) {
        warnings.push(`${vehicleImageStats.missingCount} vehicle images not found`);
      }
      if (documentFileStats.missingCount > 0) {
        warnings.push(`${documentFileStats.missingCount} document files not found`);
      }

      const manifest: UserExportManifest = {
        version: USER_EXPORT_CONFIG.archiveVersion,
        createdAt: new Date().toISOString(),
        applicationVersion: process.env.npm_package_version || '1.0.0',
        userId,
        contents: {
          vehicles: {
            count: vehicles.length,
            withImages: vehicles.filter(v => v.imageStorageKey).length,
          },
          fuelLogs: { count: fuelLogs.length },
          documents: {
            count: documents.length,
            withFiles: documents.filter(d => d.storageKey).length,
          },
          maintenanceRecords: { count: maintenanceRecords.length },
          maintenanceSchedules: { count: maintenanceSchedules.length },
        },
        files: {
          vehicleImages: vehicleImageStats.copiedCount,
          documentFiles: documentFileStats.copiedCount,
          totalSizeBytes: vehicleImageStats.totalSize + documentFileStats.totalSize,
        },
        warnings,
      };

      await fsp.writeFile(
        path.join(workDir, 'manifest.json'),
        JSON.stringify(manifest, null, 2)
      );

      // Create tar.gz archive
      const archiveFilename = `motovaultpro_export_${timestamp}.tar.gz`;
      const archivePath = path.join(this.tempPath, archiveFilename);

      logger.info('Creating tar.gz archive', { userId, archivePath });
      await tar.create(
        {
          gzip: true,
          file: archivePath,
          cwd: workDir,
        },
        ['.']
      );

      const archiveStats = await fsp.stat(archivePath);

      logger.info('User export archive created successfully', {
        userId,
        archivePath,
        sizeBytes: archiveStats.size,
      });

      // Create read stream for download
      const stream = fs.createReadStream(archivePath);

      // Schedule cleanup after stream ends
      stream.on('end', async () => {
        await this.cleanupWorkDir(workDir);
        await this.cleanupArchive(archivePath);
      });

      stream.on('error', async () => {
        await this.cleanupWorkDir(workDir);
        await this.cleanupArchive(archivePath);
      });

      return {
        stream,
        filename: archiveFilename,
        size: archiveStats.size,
      };
    } catch (error) {
      // Cleanup on error
      await this.cleanupWorkDir(workDir);
      throw error;
    }
  }

  /**
   * Copies vehicle images to the export archive
   */
  private async copyVehicleImages(
    workDir: string,
    vehicles: any[]
  ): Promise<{ copiedCount: number; missingCount: number; totalSize: number }> {
    let copiedCount = 0;
    let missingCount = 0;
    let totalSize = 0;

    for (const vehicle of vehicles) {
      if (!vehicle.imageStorageBucket || !vehicle.imageStorageKey) {
        continue;
      }

      const sourcePath = path.join(
        this.dataPath,
        vehicle.imageStorageBucket,
        vehicle.imageStorageKey
      );

      const destDir = path.join(workDir, 'files', 'vehicle-images', vehicle.id);
      const destPath = path.join(destDir, vehicle.imageFileName || 'image.jpg');

      try {
        await fsp.mkdir(destDir, { recursive: true });
        await fsp.copyFile(sourcePath, destPath);

        const stats = await fsp.stat(destPath);
        totalSize += stats.size;
        copiedCount++;
      } catch (error) {
        logger.warn('Failed to copy vehicle image', {
          vehicleId: vehicle.id,
          sourcePath,
          error: error instanceof Error ? error.message : String(error),
        });
        missingCount++;
      }
    }

    return { copiedCount, missingCount, totalSize };
  }

  /**
   * Copies document files to the export archive
   */
  private async copyDocumentFiles(
    workDir: string,
    documents: any[]
  ): Promise<{ copiedCount: number; missingCount: number; totalSize: number }> {
    let copiedCount = 0;
    let missingCount = 0;
    let totalSize = 0;

    for (const doc of documents) {
      if (!doc.storageBucket || !doc.storageKey) {
        continue;
      }

      const sourcePath = path.join(this.dataPath, doc.storageBucket, doc.storageKey);

      const destDir = path.join(workDir, 'files', 'documents', doc.id);
      const destPath = path.join(destDir, doc.fileName || 'file.pdf');

      try {
        await fsp.mkdir(destDir, { recursive: true });
        await fsp.copyFile(sourcePath, destPath);

        const stats = await fsp.stat(destPath);
        totalSize += stats.size;
        copiedCount++;
      } catch (error) {
        logger.warn('Failed to copy document file', {
          documentId: doc.id,
          sourcePath,
          error: error instanceof Error ? error.message : String(error),
        });
        missingCount++;
      }
    }

    return { copiedCount, missingCount, totalSize };
  }

  /**
   * Cleans up a working directory
   */
  private async cleanupWorkDir(workDir: string): Promise<void> {
    try {
      await fsp.rm(workDir, { recursive: true, force: true });
      logger.debug('Cleaned up work directory', { workDir });
    } catch (error) {
      logger.warn('Failed to cleanup work directory', {
        workDir,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }

  /**
   * Cleans up an archive file
   */
  private async cleanupArchive(archivePath: string): Promise<void> {
    try {
      await fsp.unlink(archivePath);
      logger.debug('Cleaned up archive file', { archivePath });
    } catch (error) {
      logger.warn('Failed to cleanup archive file', {
        archivePath,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }
}
