/**
 * Checkpoint Manager
 *
 * Saves and restores flow state at each step boundary.
 * Enables time-travel debugging: rewind, branch, and resume.
 *
 * Checkpoints are stored in .sandcode/checkpoints/ as JSON files
 * alongside git refs (commit or stash).
 */

import { readFile, writeFile, mkdir, readdir } from 'fs/promises'
import { join } from 'path'
import { execFile } from 'child_process'
import { promisify } from 'util'
import { getCwd } from '../../utils/cwd.js'
import { logForDebugging } from '../../utils/debug.js'
import type {
  FlowCheckpoint,
  FlowId,
  CheckpointId,
  FlowState,
  TestResults,
} from './types.js'
import { createCheckpointId, FLOW_DEFAULTS } from './types.js'

const execFileAsync = promisify(execFile)

/**
 * Save a checkpoint for the current flow state.
 *
 * 1. Stages and commits (or stashes) current changes
 * 2. Saves context summary and test results
 * 3. Records the git ref for restoration
 */
export async function saveCheckpoint(
  flowState: FlowState,
  stepIndex: number,
  description: string,
  contextSummary: string,
  testResults?: TestResults,
): Promise<FlowCheckpoint> {
  const checkpointId = createCheckpointId(stepIndex)

  // Create a git stash or commit to preserve state
  const gitRef = await createGitRef(flowState.id, stepIndex)

  const completedSteps = flowState.plan?.steps
    .filter(s => s.status === 'completed')
    .map(s => s.index) ?? []

  const checkpoint: FlowCheckpoint = {
    id: checkpointId,
    flowId: flowState.id,
    stepIndex,
    description,
    gitRef,
    contextSummary,
    testResults,
    completedSteps,
    createdAt: Date.now(),
  }

  // Persist checkpoint
  await persistCheckpoint(checkpoint)

  logForDebugging(`Checkpoint saved: ${checkpointId} at step ${stepIndex} (git: ${gitRef})`)
  return checkpoint
}

/**
 * Restore flow state to a specific checkpoint.
 *
 * 1. Restores git state from the checkpoint's ref
 * 2. Returns the checkpoint data (caller reconstructs context)
 */
export async function restoreCheckpoint(
  checkpointId: CheckpointId,
  flowId: FlowId,
): Promise<FlowCheckpoint | null> {
  const checkpoint = await loadCheckpoint(checkpointId, flowId)
  if (!checkpoint) {
    logForDebugging(`Checkpoint not found: ${checkpointId}`)
    return null
  }

  // Restore git state
  await restoreGitRef(checkpoint.gitRef)

  logForDebugging(`Checkpoint restored: ${checkpointId} (step ${checkpoint.stepIndex})`)
  return checkpoint
}

/**
 * List all checkpoints for a flow.
 */
export async function listCheckpoints(flowId: FlowId): Promise<FlowCheckpoint[]> {
  const checkpointsDir = join(getCwd(), FLOW_DEFAULTS.checkpointDir)

  try {
    const files = await readdir(checkpointsDir)
    const checkpoints: FlowCheckpoint[] = []

    for (const file of files) {
      if (!file.startsWith(`ckpt_`) || !file.endsWith('.json')) continue
      if (!file.includes(flowId)) continue

      try {
        const content = await readFile(join(checkpointsDir, file), 'utf-8')
        const checkpoint = JSON.parse(content) as FlowCheckpoint
        if (checkpoint.flowId === flowId) {
          checkpoints.push(checkpoint)
        }
      } catch {
        // Skip malformed checkpoint files
      }
    }

    return checkpoints.sort((a, b) => a.stepIndex - b.stepIndex)
  } catch {
    return []
  }
}

/**
 * Get the most recent checkpoint for a flow.
 */
export async function getLatestCheckpoint(flowId: FlowId): Promise<FlowCheckpoint | null> {
  const checkpoints = await listCheckpoints(flowId)
  return checkpoints[checkpoints.length - 1] ?? null
}

/**
 * Delete a checkpoint.
 */
export async function deleteCheckpoint(
  checkpointId: CheckpointId,
  flowId: FlowId,
): Promise<void> {
  const filepath = getCheckpointPath(checkpointId, flowId)
  try {
    const { unlink } = await import('fs/promises')
    await unlink(filepath)
  } catch {
    // File may not exist
  }
}

// --- Internal helpers ---

/**
 * Create a git ref (stash) for the current state.
 */
async function createGitRef(flowId: FlowId, stepIndex: number): Promise<string> {
  const message = `sandcode:flow:${flowId}:step:${stepIndex}`

  try {
    // Try to stage and stash all changes
    await execFileAsync('git', ['add', '-A'], { cwd: getCwd() })
    await execFileAsync('git', ['stash', 'push', '-m', message], { cwd: getCwd() })

    // Get the stash ref
    const { stdout } = await execFileAsync('git', ['stash', 'list'], { cwd: getCwd() })
    const stashLine = stdout.split('\n')[0]
    const stashRef = stashLine?.match(/^(stash@\{\d+\})/)?.[1] ?? 'stash@{0}'

    return stashRef
  } catch {
    // If git fails, return a fallback ref
    logForDebugging('Failed to create git stash for checkpoint, using fallback')
    return `fallback_${Date.now()}`
  }
}

/**
 * Restore git state from a ref.
 */
async function restoreGitRef(gitRef: string): Promise<void> {
  try {
    if (gitRef.startsWith('stash@')) {
      // Pop the stash (or apply if it's not the top)
      await execFileAsync('git', ['stash', 'pop', gitRef], { cwd: getCwd() })
    } else if (gitRef.startsWith('fallback_')) {
      logForDebugging('Skipping git restore for fallback checkpoint ref')
    }
  } catch (error) {
    logForDebugging(`Failed to restore git ref ${gitRef}: ${error}`)
  }
}

/**
 * Persist a checkpoint to disk.
 */
async function persistCheckpoint(checkpoint: FlowCheckpoint): Promise<void> {
  const dir = join(getCwd(), FLOW_DEFAULTS.checkpointDir)
  await mkdir(dir, { recursive: true })

  const filename = `ckpt_step${checkpoint.stepIndex}_${checkpoint.id}.json`
  const filepath = join(dir, filename)

  await writeFile(filepath, JSON.stringify(checkpoint, null, 2), 'utf-8')
}

/**
 * Load a checkpoint from disk.
 */
async function loadCheckpoint(
  checkpointId: CheckpointId,
  flowId: FlowId,
): Promise<FlowCheckpoint | null> {
  const filepath = getCheckpointPath(checkpointId, flowId)
  try {
    const content = await readFile(filepath, 'utf-8')
    return JSON.parse(content) as FlowCheckpoint
  } catch {
    return null
  }
}

/**
 * Get the file path for a checkpoint.
 */
function getCheckpointPath(checkpointId: CheckpointId, flowId: FlowId): string {
  const dir = join(getCwd(), FLOW_DEFAULTS.checkpointDir)
  // Search by checkpoint ID (the ID contains enough info to find it)
  return join(dir, `ckpt_step*_${checkpointId}.json`)
}

/**
 * Create a formatted checkpoint summary for display.
 */
export function formatCheckpointSummary(checkpoints: FlowCheckpoint[]): string {
  if (checkpoints.length === 0) {
    return 'No checkpoints found for this flow.'
  }

  const lines: string[] = ['Flow Checkpoints:', '']

  for (const cp of checkpoints) {
    const time = new Date(cp.createdAt).toLocaleTimeString()
    const status = cp.testResults?.passed ? 'PASS' : 'FAIL'
    lines.push(`  [${cp.stepIndex}] ${cp.description} (${time}) [${status}]`)
    if (cp.testResults) {
      lines.push(`       Tests: ${cp.testResults.passedCount}/${cp.testResults.total} passed`)
    }
  }

  lines.push('')
  lines.push('Use /rewind to go back, or /checkpoint <id> to restore a specific one.')
  return lines.join('\n')
}
