// @lifecycle canonical - Git checkpoint utilities for verify rollback support.
import { exec } from 'child_process';
import { promisify } from 'util';

import { Logger } from '../../../infra/logging/index.js';

const execAsync = promisify(exec);

/**
 * Result of a git checkpoint operation.
 */
export interface CheckpointResult {
  success: boolean;
  ref: string | null;
  message: string;
}

/**
 * Git checkpoint utilities for verify rollback support.
 *
 * Provides git stash-based checkpointing for safe verification:
 * - createCheckpoint(): Stash tracked changes before verification
 * - rollbackToCheckpoint(): Restore stashed changes on failure
 * - clearCheckpoint(): Drop stash if verification passes
 *
 * Only affects tracked files (untracked files are preserved).
 */
export class GitCheckpoint {
  private readonly logger: Logger;
  private readonly workingDir: string;

  constructor(logger: Logger, workingDir: string) {
    this.logger = logger;
    this.workingDir = workingDir;
  }

  /**
   * Create a git stash checkpoint before verification.
   *
   * Stashes only tracked files to preserve work in case verification fails.
   * Returns the stash reference for later rollback/clear.
   */
  async createCheckpoint(): Promise<CheckpointResult> {
    try {
      // Check if we're in a git repo
      await execAsync('git rev-parse --is-inside-work-tree', { cwd: this.workingDir });

      // Check for changes to stash (tracked files only)
      const { stdout: statusOutput } = await execAsync('git status --porcelain', {
        cwd: this.workingDir,
      });

      // Filter to only tracked files (modified/deleted, not untracked '??')
      const trackedChanges = statusOutput
        .split('\n')
        .filter((line) => line.trim() !== '' && !line.startsWith('??'));

      if (trackedChanges.length === 0) {
        this.logger.debug('[GitCheckpoint] No tracked changes to stash');
        return {
          success: true,
          ref: null,
          message: 'No tracked changes to checkpoint',
        };
      }

      // Create stash with timestamp
      const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
      const stashMessage = `verify-checkpoint-${timestamp}`;

      await execAsync(`git stash push -m "${stashMessage}"`, {
        cwd: this.workingDir,
      });

      this.logger.info(`[GitCheckpoint] Created checkpoint: ${stashMessage}`);

      return {
        success: true,
        ref: stashMessage,
        message: `Checkpointed ${trackedChanges.length} tracked file(s)`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.warn(`[GitCheckpoint] Failed to create checkpoint: ${errorMessage}`);

      return {
        success: false,
        ref: null,
        message: `Checkpoint failed: ${errorMessage}`,
      };
    }
  }

  /**
   * Rollback to a previously created checkpoint.
   *
   * Restores the stashed changes, effectively undoing work since checkpoint.
   * Uses 'git stash pop' to apply and remove the stash.
   */
  async rollbackToCheckpoint(ref: string | null): Promise<CheckpointResult> {
    if (ref === null || ref === '') {
      return {
        success: true,
        ref: null,
        message: 'No checkpoint to rollback (no changes were stashed)',
      };
    }

    try {
      // Find the stash by message
      const { stdout: stashList } = await execAsync('git stash list', {
        cwd: this.workingDir,
      });

      const stashMatch = stashList.split('\n').find((line) => line.includes(ref));

      if (stashMatch === undefined) {
        this.logger.warn(`[GitCheckpoint] Stash not found: ${ref}`);
        return {
          success: false,
          ref,
          message: `Checkpoint not found: ${ref}`,
        };
      }

      // Extract stash index (e.g., "stash@{0}")
      const stashIndex = stashMatch.split(':')[0];

      // Reset any uncommitted changes before popping (rollback discards work since checkpoint)
      await execAsync('git checkout -- .', {
        cwd: this.workingDir,
      });

      // Pop the stash to restore changes
      await execAsync(`git stash pop ${stashIndex}`, {
        cwd: this.workingDir,
      });

      this.logger.info(`[GitCheckpoint] Rolled back to checkpoint: ${ref}`);

      return {
        success: true,
        ref,
        message: `Rolled back to checkpoint: ${ref}`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.error(`[GitCheckpoint] Rollback failed: ${errorMessage}`);

      return {
        success: false,
        ref,
        message: `Rollback failed: ${errorMessage}`,
      };
    }
  }

  /**
   * Clear a checkpoint after successful verification.
   *
   * Drops the stash without applying changes (they're already in working tree).
   */
  async clearCheckpoint(ref: string | null): Promise<CheckpointResult> {
    if (ref === null || ref === '') {
      return {
        success: true,
        ref: null,
        message: 'No checkpoint to clear',
      };
    }

    try {
      // Find the stash by message
      const { stdout: stashList } = await execAsync('git stash list', {
        cwd: this.workingDir,
      });

      const stashMatch = stashList.split('\n').find((line) => line.includes(ref));

      if (stashMatch === undefined) {
        // Already cleared or never existed - that's fine
        return {
          success: true,
          ref,
          message: 'Checkpoint already cleared',
        };
      }

      // Extract stash index
      const stashIndex = stashMatch.split(':')[0];

      // Drop the stash (don't apply)
      await execAsync(`git stash drop ${stashIndex}`, {
        cwd: this.workingDir,
      });

      this.logger.debug(`[GitCheckpoint] Cleared checkpoint: ${ref}`);

      return {
        success: true,
        ref,
        message: `Cleared checkpoint: ${ref}`,
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.logger.warn(`[GitCheckpoint] Failed to clear checkpoint: ${errorMessage}`);

      // Non-fatal - don't fail the whole operation
      return {
        success: true,
        ref,
        message: `Clear checkpoint warning: ${errorMessage}`,
      };
    }
  }
}

/**
 * Factory function to create a GitCheckpoint instance.
 */
export function createGitCheckpoint(logger: Logger, workingDir: string): GitCheckpoint {
  return new GitCheckpoint(logger, workingDir);
}
