/**
 * BackupService - Gestión de Backups
 *
 * Administra creación, verificación y restauración de backups.
 *
 * @module Admin
 */

import { Repository } from 'typeorm';
import { Backup, BackupType, BackupStatus, BackupStorage } from '../entities/backup.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreateBackupDto {
  backupType: BackupType;
  name: string;
  description?: string;
  storageLocation?: BackupStorage;
  tablesIncluded?: string[];
  tablesExcluded?: string[];
  retentionPolicy?: string;
  isScheduled?: boolean;
  scheduleId?: string;
  metadata?: Record<string, any>;
}

export interface BackupFilters {
  backupType?: BackupType;
  status?: BackupStatus;
  storageLocation?: BackupStorage;
  isScheduled?: boolean;
  isVerified?: boolean;
  dateFrom?: Date;
  dateTo?: Date;
}

export class BackupService {
  private repository: Repository<Backup>;

  constructor(repository: Repository<Backup>) {
    this.repository = repository;
  }

  async create(ctx: ServiceContext, data: Partial<Backup>): Promise<Backup> {
    const entity = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdById: ctx.userId,
    });
    return this.repository.save(entity);
  }

  async findById(ctx: ServiceContext, id: string): Promise<Backup | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId } as any,
    });
  }

  async update(ctx: ServiceContext, id: string, data: Partial<Backup>): Promise<Backup | null> {
    const entity = await this.findById(ctx, id);
    if (!entity) return null;
    Object.assign(entity, data);
    return this.repository.save(entity);
  }

  async softDelete(ctx: ServiceContext, id: string): Promise<boolean> {
    const result = await this.repository.delete({ id, tenantId: ctx.tenantId } as any);
    return (result.affected || 0) > 0;
  }

  /**
   * Buscar backups con filtros
   */
  async findWithFilters(
    ctx: ServiceContext,
    filters: BackupFilters,
    page = 1,
    limit = 20
  ): Promise<PaginatedResult<Backup>> {
    const qb = this.repository
      .createQueryBuilder('b')
      .leftJoinAndSelect('b.createdBy', 'cb')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId });

    if (filters.backupType) {
      qb.andWhere('b.backup_type = :backupType', { backupType: filters.backupType });
    }
    if (filters.status) {
      qb.andWhere('b.status = :status', { status: filters.status });
    }
    if (filters.storageLocation) {
      qb.andWhere('b.storage_location = :storageLocation', { storageLocation: filters.storageLocation });
    }
    if (filters.isScheduled !== undefined) {
      qb.andWhere('b.is_scheduled = :isScheduled', { isScheduled: filters.isScheduled });
    }
    if (filters.isVerified !== undefined) {
      qb.andWhere('b.is_verified = :isVerified', { isVerified: filters.isVerified });
    }
    if (filters.dateFrom) {
      qb.andWhere('b.created_at >= :dateFrom', { dateFrom: filters.dateFrom });
    }
    if (filters.dateTo) {
      qb.andWhere('b.created_at <= :dateTo', { dateTo: filters.dateTo });
    }

    const skip = (page - 1) * limit;
    qb.orderBy('b.created_at', 'DESC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Iniciar backup
   */
  async initiateBackup(ctx: ServiceContext, data: CreateBackupDto): Promise<Backup> {
    // Calculate expiration based on retention policy
    let expiresAt: Date | null = null;
    if (data.retentionPolicy) {
      expiresAt = this.calculateExpiration(data.retentionPolicy);
    }

    const backup = await this.create(ctx, {
      ...data,
      status: 'pending' as BackupStatus,
      isEncrypted: true,
      isCompressed: true,
      compressionType: 'gzip',
      expiresAt,
    });

    // In production, this would trigger an async job
    // For now, we simulate starting the backup
    await this.startBackupProcess(ctx, backup.id);

    return this.findById(ctx, backup.id) as Promise<Backup>;
  }

  /**
   * Proceso de backup (simulado)
   */
  private async startBackupProcess(ctx: ServiceContext, backupId: string): Promise<void> {
    const startTime = Date.now();

    await this.update(ctx, backupId, {
      status: 'running' as BackupStatus,
      startedAt: new Date(),
    });

    // Simulate backup process
    // In production, this would be handled by a job queue
    const duration = Date.now() - startTime;

    await this.update(ctx, backupId, {
      status: 'completed' as BackupStatus,
      completedAt: new Date(),
      durationSeconds: Math.ceil(duration / 1000),
      // These would be real values from the backup process
      fileSize: 0,
      rowCount: 0,
    });
  }

  /**
   * Calcular fecha de expiración
   */
  private calculateExpiration(policy: string): Date | null {
    const now = new Date();
    switch (policy) {
      case 'daily':
        now.setDate(now.getDate() + 7); // Keep 7 days
        return now;
      case 'weekly':
        now.setDate(now.getDate() + 30); // Keep 30 days
        return now;
      case 'monthly':
        now.setMonth(now.getMonth() + 12); // Keep 12 months
        return now;
      case 'yearly':
        now.setFullYear(now.getFullYear() + 7); // Keep 7 years
        return now;
      case 'permanent':
        return null; // Never expires
      default:
        now.setDate(now.getDate() + 30); // Default 30 days
        return now;
    }
  }

  /**
   * Marcar backup como verificado
   */
  async markVerified(ctx: ServiceContext, id: string): Promise<Backup | null> {
    const backup = await this.findById(ctx, id);
    if (!backup) {
      return null;
    }

    if (backup.status !== 'completed') {
      throw new Error('Only completed backups can be verified');
    }

    return this.update(ctx, id, {
      isVerified: true,
      verifiedAt: new Date(),
      verifiedById: ctx.userId,
    });
  }

  /**
   * Cancelar backup en progreso
   */
  async cancelBackup(ctx: ServiceContext, id: string): Promise<Backup | null> {
    const backup = await this.findById(ctx, id);
    if (!backup) {
      return null;
    }

    if (!['pending', 'running'].includes(backup.status)) {
      throw new Error('Only pending or running backups can be cancelled');
    }

    return this.update(ctx, id, {
      status: 'cancelled' as BackupStatus,
      completedAt: new Date(),
    });
  }

  /**
   * Obtener último backup exitoso
   */
  async getLastSuccessful(ctx: ServiceContext, backupType?: BackupType): Promise<Backup | null> {
    const qb = this.repository
      .createQueryBuilder('b')
      .where('b.tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('b.status = :status', { status: 'completed' });

    if (backupType) {
      qb.andWhere('b.backup_type = :backupType', { backupType });
    }

    return qb.orderBy('b.completed_at', 'DESC').getOne();
  }

  /**
   * Obtener backups pendientes de verificación
   */
  async getPendingVerification(ctx: ServiceContext): Promise<Backup[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        status: 'completed' as BackupStatus,
        isVerified: false,
      } as any,
      order: { completedAt: 'DESC' },
    });
  }

  /**
   * Limpiar backups expirados
   */
  async cleanupExpired(ctx: ServiceContext): Promise<number> {
    const result = await this.repository
      .createQueryBuilder()
      .update(Backup)
      .set({ status: 'expired' as BackupStatus })
      .where('tenant_id = :tenantId', { tenantId: ctx.tenantId })
      .andWhere('expires_at < NOW()')
      .andWhere('status = :status', { status: 'completed' })
      .execute();

    return result.affected || 0;
  }

  /**
   * Obtener estadísticas
   */
  async getStats(ctx: ServiceContext): Promise<BackupStats> {
    const all = await this.repository.find({
      where: { tenantId: ctx.tenantId } as any,
    });

    const byType = new Map<BackupType, number>();
    const byStatus = new Map<BackupStatus, number>();
    let totalSize = 0;
    let verifiedCount = 0;

    for (const backup of all) {
      byType.set(backup.backupType, (byType.get(backup.backupType) || 0) + 1);
      byStatus.set(backup.status, (byStatus.get(backup.status) || 0) + 1);
      totalSize += Number(backup.fileSize) || 0;
      if (backup.isVerified) verifiedCount++;
    }

    const lastBackup = await this.getLastSuccessful(ctx);

    return {
      total: all.length,
      totalSizeBytes: totalSize,
      totalSizeHuman: this.formatBytes(totalSize),
      verifiedCount,
      byType: Array.from(byType.entries()).map(([type, count]) => ({ type, count })),
      byStatus: Array.from(byStatus.entries()).map(([status, count]) => ({ status, count })),
      lastBackupAt: lastBackup?.completedAt || null,
    };
  }

  /**
   * Formatear bytes a formato legible
   */
  private formatBytes(bytes: number): string {
    if (bytes === 0) return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
  }
}

export interface BackupStats {
  total: number;
  totalSizeBytes: number;
  totalSizeHuman: string;
  verifiedCount: number;
  byType: { type: BackupType; count: number }[];
  byStatus: { status: BackupStatus; count: number }[];
  lastBackupAt: Date | null;
}
