/**
 * @ai-summary Core backup service for create, list, download, delete operations
 * @ai-context Orchestrates backup operations and sends notifications
 */

import * as fs from 'fs';
import * as fsp from 'fs/promises';
import * as path from 'path';
import { Pool } from 'pg';
import { logger } from '../../../core/logging/logger';
import { BackupRepository } from '../data/backup.repository';
import { BackupArchiveService } from './backup-archive.service';
import {
  BACKUP_CONFIG,
  BackupHistory,
  BackupSchedule,
  BackupSettingsMap,
  CreateBackupOptions,
  BackupResult,
  ListBackupsParams,
  ListBackupsResponse,
  BackupFrequency,
  ScheduleResponse,
} from './backup.types';
import { classifyAndCalculateExpiration } from './backup-classification.service';

export class BackupService {
  private repository: BackupRepository;
  private archiveService: BackupArchiveService;

  constructor(pool: Pool) {
    this.repository = new BackupRepository(pool);
    this.archiveService = new BackupArchiveService();
  }

  // ============================================
  // Backup Operations
  // ============================================

  /**
   * Creates a new backup
   */
  async createBackup(options: CreateBackupOptions): Promise<BackupResult> {
    const now = new Date();
    const timestamp = now.toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const tempFilename = `backup_${timestamp}`;

    // Classify the backup based on its creation timestamp
    const { categories, expiresAt } = classifyAndCalculateExpiration(now);

    // Create initial backup record with classification
    const backupRecord = await this.repository.createBackupRecord({
      scheduleId: options.scheduleId,
      backupType: options.backupType,
      filename: tempFilename,
      filePath: '',
      fileSizeBytes: 0,
      createdBy: options.createdBy,
      metadata: { name: options.name },
      categories,
      expiresAt,
    });

    logger.info('Starting backup creation', {
      backupId: backupRecord.id,
      backupType: options.backupType,
      scheduleName: options.name,
      categories,
      expiresAt: expiresAt.toISOString(),
    });

    try {
      // Get schedule name if applicable
      let scheduleName: string | undefined;
      if (options.scheduleId) {
        const schedule = await this.repository.getScheduleById(options.scheduleId);
        scheduleName = schedule?.name;
      }

      // Create the archive
      const result = await this.archiveService.createArchive(backupRecord.id, {
        scheduleName: scheduleName || options.name,
        backupType: options.backupType,
        includeDocuments: options.includeDocuments,
        createdBy: options.createdBy || undefined,
      });

      // Update the backup record with final details
      const filename = path.basename(result.archivePath);
      await this.repository.completeBackup(backupRecord.id, {
        fileSizeBytes: result.sizeBytes,
        databaseTablesCount: result.manifest.contents.database.tablesCount,
        documentsCount: result.manifest.contents.documents.totalFiles,
        filename,
        filePath: result.archivePath,
        metadata: {
          ...result.manifest.metadata,
          filename,
          archivePath: result.archivePath,
        },
      });

      logger.info('Backup created successfully', {
        backupId: backupRecord.id,
        filename,
        sizeBytes: result.sizeBytes,
      });

      // Send success notification
      await this.sendNotification('success', backupRecord.id);

      return {
        success: true,
        backupId: backupRecord.id,
        filePath: result.archivePath,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);

      logger.error('Backup creation failed', {
        backupId: backupRecord.id,
        error: errorMessage,
      });

      // Update record with failure
      await this.repository.failBackup(backupRecord.id, errorMessage);

      // Send failure notification
      await this.sendNotification('failure', backupRecord.id, errorMessage);

      return {
        success: false,
        backupId: backupRecord.id,
        error: errorMessage,
      };
    }
  }

  /**
   * Lists backups with pagination
   */
  async listBackups(params: ListBackupsParams): Promise<ListBackupsResponse> {
    const page = params.page || 1;
    const pageSize = params.pageSize || 20;

    const { items, total } = await this.repository.listBackups(params);

    return {
      items,
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  /**
   * Gets a single backup by ID
   */
  async getBackup(id: string): Promise<BackupHistory | null> {
    return this.repository.getBackupById(id);
  }

  /**
   * Gets the file path for downloading a backup
   */
  async getBackupFilePath(id: string): Promise<string | null> {
    const backup = await this.repository.getBackupById(id);
    if (!backup || backup.status !== 'completed') {
      return null;
    }

    const filePath = (backup.metadata as any)?.archivePath || backup.filePath;
    if (!filePath) {
      return null;
    }

    // Verify file exists
    try {
      await fsp.access(filePath);
      return filePath;
    } catch {
      return null;
    }
  }

  /**
   * Creates a readable stream for downloading a backup
   */
  async createDownloadStream(id: string): Promise<{
    stream: fs.ReadStream;
    filename: string;
    size: number;
  } | null> {
    const filePath = await this.getBackupFilePath(id);
    if (!filePath) {
      return null;
    }

    const backup = await this.repository.getBackupById(id);
    if (!backup) {
      return null;
    }

    const stats = await fsp.stat(filePath);
    const filename = path.basename(filePath);

    return {
      stream: fs.createReadStream(filePath),
      filename,
      size: stats.size,
    };
  }

  /**
   * Deletes a backup (file and record)
   */
  async deleteBackup(id: string): Promise<boolean> {
    const backup = await this.repository.getBackupById(id);
    if (!backup) {
      return false;
    }

    // Delete the file if it exists
    const filePath = (backup.metadata as any)?.archivePath || backup.filePath;
    if (filePath) {
      try {
        await fsp.unlink(filePath);
        logger.info('Backup file deleted', { backupId: id, filePath });
      } catch (error) {
        // File might not exist, continue with record deletion
        logger.warn('Failed to delete backup file', {
          backupId: id,
          filePath,
          error: error instanceof Error ? error.message : String(error),
        });
      }
    }

    // Delete the database record
    return this.repository.deleteBackupRecord(id);
  }

  /**
   * Imports an uploaded backup file
   */
  async importUploadedBackup(
    filePath: string,
    originalFilename: string,
    createdBy?: string
  ): Promise<BackupHistory> {
    // Validate the archive
    const validation = await this.archiveService.validateArchive(filePath);
    if (!validation.valid) {
      throw new Error(`Invalid backup archive: ${validation.error}`);
    }

    const manifest = validation.manifest!;
    const stats = await fsp.stat(filePath);

    // Move file to backup storage
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    const destFilename = `uploaded_${timestamp}_${path.basename(originalFilename)}`;
    const destPath = path.join(BACKUP_CONFIG.storagePath, destFilename);

    await fsp.mkdir(BACKUP_CONFIG.storagePath, { recursive: true });
    await fsp.copyFile(filePath, destPath);
    await fsp.unlink(filePath);

    // Create backup record
    const record = await this.repository.createBackupRecord({
      scheduleId: null,
      backupType: 'manual',
      filename: destFilename,
      filePath: destPath,
      fileSizeBytes: stats.size,
      createdBy,
      metadata: {
        originalFilename,
        imported: true,
        manifestVersion: manifest.version,
        originalCreatedAt: manifest.createdAt,
      },
    });

    // Complete the record immediately since it's already a valid backup
    return (await this.repository.completeBackup(record.id, {
      fileSizeBytes: stats.size,
      databaseTablesCount: manifest.contents.database.tablesCount,
      documentsCount: manifest.contents.documents.totalFiles,
      metadata: {
        archivePath: destPath,
        originalFilename,
        imported: true,
      },
    }))!;
  }

  // ============================================
  // Schedule Operations
  // ============================================

  /**
   * Lists all backup schedules with last backup info
   */
  async listSchedules(): Promise<ScheduleResponse[]> {
    const schedules = await this.repository.listSchedules();
    const result: ScheduleResponse[] = [];

    for (const schedule of schedules) {
      const lastBackup = await this.repository.getLastBackupForSchedule(schedule.id);
      result.push({
        ...schedule,
        lastBackup: lastBackup || undefined,
      });
    }

    return result;
  }

  /**
   * Gets a single schedule by ID
   */
  async getSchedule(id: string): Promise<ScheduleResponse | null> {
    const schedule = await this.repository.getScheduleById(id);
    if (!schedule) {
      return null;
    }

    const lastBackup = await this.repository.getLastBackupForSchedule(id);
    return {
      ...schedule,
      lastBackup: lastBackup || undefined,
    };
  }

  /**
   * Creates a new backup schedule
   */
  async createSchedule(
    name: string,
    frequency: BackupFrequency,
    retentionCount?: number,
    isEnabled?: boolean
  ): Promise<BackupSchedule> {
    return this.repository.createSchedule(
      name,
      frequency,
      retentionCount,
      isEnabled
    );
  }

  /**
   * Updates a backup schedule
   */
  async updateSchedule(
    id: string,
    updates: Partial<{
      name: string;
      frequency: BackupFrequency;
      retentionCount: number;
      isEnabled: boolean;
    }>
  ): Promise<BackupSchedule | null> {
    return this.repository.updateSchedule(id, updates);
  }

  /**
   * Deletes a backup schedule
   */
  async deleteSchedule(id: string): Promise<boolean> {
    return this.repository.deleteSchedule(id);
  }

  /**
   * Toggles a schedule's enabled state
   */
  async toggleSchedule(id: string): Promise<BackupSchedule | null> {
    const schedule = await this.repository.getScheduleById(id);
    if (!schedule) {
      return null;
    }

    return this.repository.updateSchedule(id, { isEnabled: !schedule.isEnabled });
  }

  // ============================================
  // Settings Operations
  // ============================================

  /**
   * Gets backup settings
   */
  async getSettings(): Promise<BackupSettingsMap> {
    return this.repository.getSettings();
  }

  /**
   * Updates backup settings
   */
  async updateSettings(updates: Partial<BackupSettingsMap>): Promise<BackupSettingsMap> {
    await this.repository.updateSettings(updates);
    return this.repository.getSettings();
  }

  // ============================================
  // Notification Helpers
  // ============================================

  /**
   * Sends backup notification email
   */
  private async sendNotification(
    type: 'success' | 'failure',
    backupId: string,
    errorMessage?: string
  ): Promise<void> {
    try {
      const settings = await this.repository.getSettings();

      // Check if notifications are enabled
      if (type === 'success' && !settings.emailOnSuccess) {
        return;
      }
      if (type === 'failure' && !settings.emailOnFailure) {
        return;
      }

      // Check if admin email is configured
      if (!settings.adminEmail) {
        logger.debug('Backup notification skipped: no admin email configured');
        return;
      }

      const backup = await this.repository.getBackupById(backupId);
      if (!backup) {
        return;
      }

      // TODO: Integrate with email service when implementing notifications
      // For now, just log the notification
      logger.info('Backup notification would be sent', {
        type,
        backupId,
        adminEmail: settings.adminEmail,
        errorMessage,
      });
    } catch (error) {
      logger.error('Failed to send backup notification', {
        type,
        backupId,
        error: error instanceof Error ? error.message : String(error),
      });
    }
  }
}
