/**
 * Checkpoint Manager for Generation Process
 * Enables rollback and recovery from partial failures
 */

import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

export interface Checkpoint {
  id: string;
  timestamp: string;
  stage: GenerationStage;
  component?: string;
  filesCreated: string[];
  filesModified: Array<{ path: string; backup: string }>;
  directoriesCreated: string[];
  metadata: Record<string, any>;
}

export enum GenerationStage {
  VALIDATION = 'validation',
  ANALYSIS = 'analysis',
  INFRASTRUCTURE = 'infrastructure',
  COMPONENT_GENERATION = 'component_generation',
  CONFIGURATION = 'configuration',
  FINALIZATION = 'finalization',
  COMPLETE = 'complete',
}

export class CheckpointManager {
  private checkpoints: Checkpoint[] = [];
  private checkpointDir: string;
  private currentCheckpoint?: Checkpoint;

  constructor(private outputDir: string) {
    this.checkpointDir = path.join(outputDir, '.bangstack', 'checkpoints');
  }

  /**
   * Initialize checkpoint system
   */
  initialize(): void {
    if (!fs.existsSync(this.checkpointDir)) {
      fs.mkdirSync(this.checkpointDir, { recursive: true });
    }

    // Load existing checkpoints
    this.loadCheckpoints();
  }

  /**
   * Create a new checkpoint
   */
  createCheckpoint(stage: GenerationStage, component?: string, metadata?: Record<string, any>): string {
    const id = this.generateCheckpointId();

    const checkpoint: Checkpoint = {
      id,
      timestamp: new Date().toISOString(),
      stage,
      component,
      filesCreated: [],
      filesModified: [],
      directoriesCreated: [],
      metadata: metadata || {},
    };

    this.checkpoints.push(checkpoint);
    this.currentCheckpoint = checkpoint;
    this.saveCheckpoint(checkpoint);

    return id;
  }

  /**
   * Record file creation
   */
  recordFileCreated(filePath: string): void {
    if (this.currentCheckpoint) {
      this.currentCheckpoint.filesCreated.push(filePath);
      this.saveCheckpoint(this.currentCheckpoint);
    }
  }

  /**
   * Record file modification with backup
   */
  recordFileModified(filePath: string): void {
    if (this.currentCheckpoint && fs.existsSync(filePath)) {
      const backupPath = this.createBackup(filePath);
      this.currentCheckpoint.filesModified.push({ path: filePath, backup: backupPath });
      this.saveCheckpoint(this.currentCheckpoint);
    }
  }

  /**
   * Record directory creation
   */
  recordDirectoryCreated(dirPath: string): void {
    if (this.currentCheckpoint) {
      this.currentCheckpoint.directoriesCreated.push(dirPath);
      this.saveCheckpoint(this.currentCheckpoint);
    }
  }

  /**
   * Rollback to a specific checkpoint
   */
  rollback(checkpointId?: string): RollbackResult {
    const checkpoint = checkpointId
      ? this.checkpoints.find(c => c.id === checkpointId)
      : this.getLastSuccessfulCheckpoint();

    if (!checkpoint) {
      return {
        success: false,
        message: 'No checkpoint found to rollback to',
      };
    }

    const result: RollbackResult = {
      success: true,
      message: `Rolling back to checkpoint: ${checkpoint.stage}`,
      filesRemoved: [],
      filesRestored: [],
      directoriesRemoved: [],
    };

    try {
      // Get all checkpoints after the target
      const checkpointsToRollback = this.checkpoints.filter(
        c => new Date(c.timestamp) > new Date(checkpoint.timestamp)
      );

      // Rollback in reverse order
      for (const cp of checkpointsToRollback.reverse()) {
        // Remove created files
        for (const filePath of cp.filesCreated) {
          if (fs.existsSync(filePath)) {
            fs.unlinkSync(filePath);
            result.filesRemoved!.push(filePath);
          }
        }

        // Restore modified files from backup
        for (const { path: filePath, backup } of cp.filesModified) {
          if (fs.existsSync(backup)) {
            fs.copyFileSync(backup, filePath);
            fs.unlinkSync(backup);
            result.filesRestored!.push(filePath);
          }
        }

        // Remove created directories (if empty)
        for (const dirPath of cp.directoriesCreated) {
          if (fs.existsSync(dirPath) && this.isDirectoryEmpty(dirPath)) {
            fs.rmdirSync(dirPath);
            result.directoriesRemoved!.push(dirPath);
          }
        }

        // Remove checkpoint file
        this.removeCheckpoint(cp.id);
      }

      return result;
    } catch (error) {
      return {
        success: false,
        message: `Rollback failed: ${error instanceof Error ? error.message : String(error)}`,
      };
    }
  }

  /**
   * Get generation progress
   */
  getProgress(): GenerationProgress {
    if (this.checkpoints.length === 0) {
      return {
        stage: GenerationStage.VALIDATION,
        percentage: 0,
        completedStages: [],
      };
    }

    const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
    const allStages = Object.values(GenerationStage);
    const currentStageIndex = allStages.indexOf(lastCheckpoint.stage);
    // Add 1 to index to show that we've completed the current stage
    const percentage = Math.round(((currentStageIndex + 1) / allStages.length) * 100);

    return {
      stage: lastCheckpoint.stage,
      percentage,
      completedStages: this.checkpoints.map(c => c.stage),
      currentComponent: lastCheckpoint.component,
    };
  }

  /**
   * Get last successful checkpoint
   */
  getLastSuccessfulCheckpoint(): Checkpoint | undefined {
    return this.checkpoints[this.checkpoints.length - 1];
  }

  /**
   * Check if can resume from checkpoint
   */
  canResume(): boolean {
    return this.checkpoints.length > 0 &&
           this.checkpoints[this.checkpoints.length - 1].stage !== GenerationStage.COMPLETE;
  }

  /**
   * Get resume point
   */
  getResumePoint(): { stage: GenerationStage; component?: string } | null {
    if (!this.canResume()) {
      return null;
    }

    const lastCheckpoint = this.checkpoints[this.checkpoints.length - 1];
    return {
      stage: lastCheckpoint.stage,
      component: lastCheckpoint.component,
    };
  }

  /**
   * Clean up checkpoints after successful completion
   */
  cleanup(): void {
    // Keep the complete checkpoint for history
    const completeCheckpoint = this.checkpoints.find(c => c.stage === GenerationStage.COMPLETE);

    if (completeCheckpoint) {
      // Remove all other checkpoints
      for (const checkpoint of this.checkpoints) {
        if (checkpoint.id !== completeCheckpoint.id) {
          this.removeCheckpoint(checkpoint.id);
        }
      }

      // Clean up backup files
      const backupDir = path.join(this.checkpointDir, 'backups');
      if (fs.existsSync(backupDir)) {
        fs.rmSync(backupDir, { recursive: true, force: true });
      }
    }
  }

  /**
   * Get checkpoint summary for user
   */
  getSummary(): string {
    if (this.checkpoints.length === 0) {
      return 'No checkpoints found';
    }

    const lines: string[] = [];
    lines.push('\n📊 Generation Progress:\n');

    const progress = this.getProgress();
    lines.push(`   Stage: ${progress.stage}`);
    lines.push(`   Progress: ${progress.percentage}%`);

    if (progress.currentComponent) {
      lines.push(`   Current: ${progress.currentComponent}`);
    }

    lines.push('\n   Checkpoints:');
    this.checkpoints.forEach((cp, _index) => {
      const icon = cp.stage === GenerationStage.COMPLETE ? '✓' : '○';
      lines.push(`     ${icon} ${cp.stage}${cp.component ? ` (${cp.component})` : ''}`);
    });

    if (this.canResume()) {
      lines.push('\n   💡 Can resume from last checkpoint');
      lines.push('      Run: bang generate <workflow> --resume');
    }

    return lines.join('\n');
  }

  // Private helper methods

  private generateCheckpointId(): string {
    return `cp_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
  }

  private saveCheckpoint(checkpoint: Checkpoint): void {
    const checkpointPath = path.join(this.checkpointDir, `${checkpoint.id}.json`);
    fs.writeFileSync(checkpointPath, JSON.stringify(checkpoint, null, 2));
  }

  private loadCheckpoints(): void {
    if (!fs.existsSync(this.checkpointDir)) {
      return;
    }

    const files = fs.readdirSync(this.checkpointDir);
    this.checkpoints = files
      .filter(f => f.endsWith('.json'))
      .map(f => {
        const content = fs.readFileSync(path.join(this.checkpointDir, f), 'utf-8');
        return JSON.parse(content) as Checkpoint;
      })
      .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
  }

  private removeCheckpoint(checkpointId: string): void {
    const checkpointPath = path.join(this.checkpointDir, `${checkpointId}.json`);
    if (fs.existsSync(checkpointPath)) {
      fs.unlinkSync(checkpointPath);
    }

    this.checkpoints = this.checkpoints.filter(c => c.id !== checkpointId);
  }

  private createBackup(filePath: string): string {
    const backupDir = path.join(this.checkpointDir, 'backups');
    if (!fs.existsSync(backupDir)) {
      fs.mkdirSync(backupDir, { recursive: true });
    }

    const timestamp = Date.now();
    const fileName = path.basename(filePath);
    const backupPath = path.join(backupDir, `${fileName}.${timestamp}.backup`);

    fs.copyFileSync(filePath, backupPath);
    return backupPath;
  }

  private isDirectoryEmpty(dirPath: string): boolean {
    const files = fs.readdirSync(dirPath);
    return files.length === 0;
  }
}

export interface RollbackResult {
  success: boolean;
  message: string;
  filesRemoved?: string[];
  filesRestored?: string[];
  directoriesRemoved?: string[];
}

export interface GenerationProgress {
  stage: GenerationStage;
  percentage: number;
  completedStages: GenerationStage[];
  currentComponent?: string;
}
