import * as plugins from './mod.plugins.js';
import * as paths from '../paths.js';
import type { IFormatOperation } from './interfaces.format.js';

export class RollbackManager {
  private backupDir: string;
  private manifestPath: string;
  
  constructor() {
    this.backupDir = plugins.path.join(paths.cwd, '.nogit', 'gitzone-backups');
    this.manifestPath = plugins.path.join(this.backupDir, 'manifest.json');
  }
  
  async createOperation(): Promise<IFormatOperation> {
    await this.ensureBackupDir();
    
    const operation: IFormatOperation = {
      id: this.generateOperationId(),
      timestamp: Date.now(),
      files: [],
      status: 'pending'
    };
    
    await this.updateManifest(operation);
    return operation;
  }
  
  async backupFile(filepath: string, operationId: string): Promise<void> {
    const operation = await this.getOperation(operationId);
    if (!operation) {
      throw new Error(`Operation ${operationId} not found`);
    }
    
    const absolutePath = plugins.path.isAbsolute(filepath) 
      ? filepath 
      : plugins.path.join(paths.cwd, filepath);
    
    // Check if file exists
    const exists = await plugins.smartfile.fs.fileExists(absolutePath);
    if (!exists) {
      // File doesn't exist yet (will be created), so we skip backup
      return;
    }
    
    // Read file content and metadata
    const content = await plugins.smartfile.fs.toStringSync(absolutePath);
    const stats = await plugins.smartfile.fs.stat(absolutePath);
    const checksum = this.calculateChecksum(content);
    
    // Create backup
    const backupPath = this.getBackupPath(operationId, filepath);
    await plugins.smartfile.fs.ensureDir(plugins.path.dirname(backupPath));
    await plugins.smartfile.memory.toFs(content, backupPath);
    
    // Update operation
    operation.files.push({
      path: filepath,
      originalContent: content,
      checksum,
      permissions: stats.mode.toString(8)
    });
    
    await this.updateManifest(operation);
  }
  
  async rollback(operationId: string): Promise<void> {
    const operation = await this.getOperation(operationId);
    if (!operation) {
      throw new Error(`Operation ${operationId} not found`);
    }
    
    if (operation.status === 'rolled-back') {
      throw new Error(`Operation ${operationId} has already been rolled back`);
    }
    
    // Restore files in reverse order
    for (let i = operation.files.length - 1; i >= 0; i--) {
      const file = operation.files[i];
      const absolutePath = plugins.path.isAbsolute(file.path) 
        ? file.path 
        : plugins.path.join(paths.cwd, file.path);
      
      // Verify backup integrity
      const backupPath = this.getBackupPath(operationId, file.path);
      const backupContent = await plugins.smartfile.fs.toStringSync(backupPath);
      const backupChecksum = this.calculateChecksum(backupContent);
      
      if (backupChecksum !== file.checksum) {
        throw new Error(`Backup integrity check failed for ${file.path}`);
      }
      
      // Restore file
      await plugins.smartfile.memory.toFs(file.originalContent, absolutePath);
      
      // Restore permissions
      const mode = parseInt(file.permissions, 8);
      // Note: Permissions restoration may not work on all platforms
    }
    
    // Update operation status
    operation.status = 'rolled-back';
    await this.updateManifest(operation);
  }
  
  async markComplete(operationId: string): Promise<void> {
    const operation = await this.getOperation(operationId);
    if (!operation) {
      throw new Error(`Operation ${operationId} not found`);
    }
    
    operation.status = 'completed';
    await this.updateManifest(operation);
  }
  
  async cleanOldBackups(retentionDays: number): Promise<void> {
    const manifest = await this.getManifest();
    const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000);
    
    const operationsToDelete = manifest.operations.filter(op => 
      op.timestamp < cutoffTime && op.status === 'completed'
    );
    
    for (const operation of operationsToDelete) {
      // Remove backup files
      const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id);
      await plugins.smartfile.fs.remove(operationDir);
      
      // Remove from manifest
      manifest.operations = manifest.operations.filter(op => op.id !== operation.id);
    }
    
    await this.saveManifest(manifest);
  }
  
  async verifyBackup(operationId: string): Promise<boolean> {
    const operation = await this.getOperation(operationId);
    if (!operation) {
      return false;
    }
    
    for (const file of operation.files) {
      const backupPath = this.getBackupPath(operationId, file.path);
      const exists = await plugins.smartfile.fs.fileExists(backupPath);
      
      if (!exists) {
        return false;
      }
      
      const content = await plugins.smartfile.fs.toStringSync(backupPath);
      const checksum = this.calculateChecksum(content);
      
      if (checksum !== file.checksum) {
        return false;
      }
    }
    
    return true;
  }
  
  async listBackups(): Promise<IFormatOperation[]> {
    const manifest = await this.getManifest();
    return manifest.operations;
  }
  
  private async ensureBackupDir(): Promise<void> {
    await plugins.smartfile.fs.ensureDir(this.backupDir);
    await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations'));
  }
  
  private generateOperationId(): string {
    const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
    const random = Math.random().toString(36).substring(2, 8);
    return `${timestamp}-${random}`;
  }
  
  private getBackupPath(operationId: string, filepath: string): string {
    const filename = plugins.path.basename(filepath);
    const dir = plugins.path.dirname(filepath);
    const safeDir = dir.replace(/[/\\]/g, '__');
    return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`);
  }
  
  private calculateChecksum(content: string | Buffer): string {
    return plugins.crypto.createHash('sha256').update(content).digest('hex');
  }
  
  private async getManifest(): Promise<{ operations: IFormatOperation[] }> {
    const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
    if (!exists) {
      return { operations: [] };
    }
    
    const content = await plugins.smartfile.fs.toStringSync(this.manifestPath);
    return JSON.parse(content);
  }
  
  private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> {
    await plugins.smartfile.memory.toFs(JSON.stringify(manifest, null, 2), this.manifestPath);
  }
  
  private async getOperation(operationId: string): Promise<IFormatOperation | null> {
    const manifest = await this.getManifest();
    return manifest.operations.find(op => op.id === operationId) || null;
  }
  
  private async updateManifest(operation: IFormatOperation): Promise<void> {
    const manifest = await this.getManifest();
    const existingIndex = manifest.operations.findIndex(op => op.id === operation.id);
    
    if (existingIndex !== -1) {
      manifest.operations[existingIndex] = operation;
    } else {
      manifest.operations.push(operation);
    }
    
    await this.saveManifest(manifest);
  }
}