/**
 * Commit Manager
 * Orchestrates the commit workflow - combines diff generation and storage
 */

import * as crypto from 'crypto';
import { execSync } from 'child_process';
import * as yaml from 'js-yaml';
import { CommitStorage } from './commit-storage';
import { DiffEngine } from './diff-engine';
import { Commit, CommitOptions } from './types';
import { WorkflowParser } from '../parser/workflow-parser';

export class CommitManager {
  private storage: CommitStorage;
  private diffEngine: DiffEngine;

  constructor(projectRoot: string) {
    this.storage = new CommitStorage(projectRoot);
    this.diffEngine = new DiffEngine();
  }

  /**
   * Initialize the commit system
   */
  initialize(): void {
    this.storage.initialize();
  }

  /**
   * Check if .bangstack directory exists
   */
  isInitialized(): boolean {
    return this.storage.exists();
  }

  /**
   * Create a new commit
   */
  async createCommit(options: CommitOptions): Promise<Commit> {
    // Ensure .bangstack exists
    if (!this.isInitialized()) {
      throw new Error('Bangstack not initialized. Run "bangstack init" first.');
    }

    // Load current config
    const currentConfig = yaml.load(this.storage.loadConfig());

    // Get previous config (from options or HEAD)
    let previousConfig = options.previousConfig;
    if (!previousConfig) {
      const head = this.storage.getHead();
      if (head) {
        // Reconstruct previous state from commits
        previousConfig = await this.reconstructConfig(head.hash);
      } else {
        // First commit - empty previous state
        previousConfig = {};
      }
    }

    // Generate diff
    const diff = this.diffEngine.generateDiff(previousConfig, currentConfig);

    if (diff.length === 0) {
      throw new Error('No changes detected. Nothing to commit.');
    }

    // Get affected files
    const filesAffected = this.diffEngine.getAffectedFiles(diff);

    // Get referenced files (from $ref)
    let referencedFiles: string[] = [];
    try {
      const parser = new WorkflowParser();
      const configPath = this.storage.getConfigPath();
      referencedFiles = parser.getReferencedFiles(configPath);
    } catch (_error) {
      // If we can't parse references, that's okay - just continue
    }

    // Get parent commit
    const index = this.storage.loadIndex();
    const parent = index.head;

    // Create commit object
    const commit: Commit = {
      hash: this.generateHash(diff, options.message),
      parent,
      timestamp: new Date().toISOString(),
      author: options.author || this.getDefaultAuthor(),
      message: options.message,
      diff,
      filesAffected,
      referencedFiles: referencedFiles.length > 0 ? referencedFiles : undefined,
    };

    // Save commit
    this.storage.saveCommit(commit);
    this.storage.updateIndex(commit);

    return commit;
  }

  /**
   * Get commit history
   */
  getHistory(limit?: number) {
    return this.storage.getHistory(limit);
  }

  /**
   * Get a specific commit
   */
  getCommit(hash: string): Commit | null {
    return this.storage.loadCommit(hash);
  }

  /**
   * Get current HEAD commit
   */
  getHead(): Commit | null {
    return this.storage.getHead();
  }

  /**
   * Reconstruct config state at a specific commit
   */
  async reconstructConfig(hash: string): Promise<any> {
    const commit = this.storage.loadCommit(hash);
    if (!commit) {
      throw new Error(`Commit not found: ${hash}`);
    }

    // Build commit chain from initial to target
    const commitChain: Commit[] = [];
    let current: Commit | null = commit;

    while (current) {
      commitChain.unshift(current);
      if (current.parent) {
        current = this.storage.loadCommit(current.parent);
      } else {
        current = null;
      }
    }

    // Apply diffs in order
    let config = {};
    for (const c of commitChain) {
      config = this.diffEngine.applyDiff(config, c.diff);
    }

    return config;
  }

  /**
   * Show diff for a commit
   */
  showCommitDiff(hash: string): { commit: Commit; summary: any } | null {
    const commit = this.storage.loadCommit(hash);
    if (!commit) {
      return null;
    }

    const summary = this.diffEngine.getSummary(commit.diff);

    return { commit, summary };
  }

  /**
   * Get changes between two commits
   */
  async getChangesBetween(fromHash: string, toHash: string): Promise<any> {
    const fromConfig = await this.reconstructConfig(fromHash);
    const toConfig = await this.reconstructConfig(toHash);

    return this.diffEngine.generateDiff(fromConfig, toConfig);
  }

  /**
   * Revert to a specific commit (dangerous!)
   */
  async revertTo(hash: string): Promise<void> {
    // Reconstruct config at target commit
    const config = await this.reconstructConfig(hash);

    // Save as new config
    const configYaml = yaml.dump(config);
    this.storage.saveConfig(configYaml);

    // Note: This doesn't create a revert commit automatically
    // User should run "bangstack commit" after reverting
  }

  /**
   * Generate hash for commit
   */
  private generateHash(diff: any, message: string): string {
    const timestamp = Date.now().toString();
    const content = JSON.stringify({ diff, message, timestamp });
    return crypto.createHash('sha256').update(content).digest('hex').substring(0, 12);
  }

  /**
   * Get default author from git config or system
   */
  private getDefaultAuthor(): string {
    // Try to get from git config
    try {
      const name = execSync('git config user.name', { encoding: 'utf-8' }).trim();
      const email = execSync('git config user.email', { encoding: 'utf-8' }).trim();
      return `${name} <${email}>`;
    } catch {
      // Fallback to system user
      return process.env.USER || process.env.USERNAME || 'unknown';
    }
  }

  /**
   * Get storage instance (for advanced usage)
   */
  getStorage(): CommitStorage {
    return this.storage;
  }

  /**
   * Get diff engine instance (for advanced usage)
   */
  getDiffEngine(): DiffEngine {
    return this.diffEngine;
  }
}
