#!/bin/bash
# =============================================================
# CLI Refactor Verification Protocol
# =============================================================
# Purpose: Deterministic verification for cli-refactor workflow with automatic
# clean baseline handling for plan artifacts vs code changes.
#
# This script implements the clean-baseline protocol by:
# 1. Distinguishing expected plan artifacts from code changes
# 2. Stashing code changes if present (preserving plan artifacts)
# 3. Running verification checks on clean baseline
# 4. Restoring stashed changes after verification
#
# Usage:
#   ./scripts/cli-refactor-verify.sh [OPTIONS]
#
# Options:
#   --check              Run verification with clean baseline
#   --stash-only         Only stash changes (don't verify, for manual workflow)
#   --restore-only       Only restore stashed changes (don't verify)
#   --dry-run            Show what would happen without executing
#   --format <fmt>       Output format: text, json (default: text)
#
# Exit Codes:
#   0 - Success (clean baseline verification passed)
#   1 - Verification failed
#   2 - Precondition error (conflicting stashes, etc.)
#   3 - Invalid arguments
#
# Environment Variables:
#   SKIP_STASH           Set to skip stashing (use existing clean baseline)
#   KEEP_STASH           Set to keep stash after restore (for debugging)
#
# =============================================================

set -euo pipefail

# Configuration
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
STASH_NAME="cli-refactor-verify-temp"
BASELINE_DIR="${BASELINE_DIR:-.anima/gate}"
EXPECTED_FILE="${EXPECTED_FILE:-${BASELINE_DIR}/expected-generated.txt}"
OUTPUT_FORMAT="text"
MODE=""
DRY_RUN=false

# Stash state tracking (for automatic cleanup)
STASH_CREATED=false

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Stash state tracking (for automatic cleanup)
STASH_CREATED=false

# Cleanup function for script EXIT (automatic restore on all exit paths)
cleanup_stash() {
    # Only restore if we created a stash and haven't kept it
    if [[ "$STASH_CREATED" == "true" ]] && [[ -z "${KEEP_STASH:-}" ]]; then
        if git stash list | grep -q "$STASH_NAME"; then
            log_warn "Cleaning up stash on exit..."
            
            # ATOMICITY: Selective restore preserves plan artifacts
            # Plan artifacts were backed up and restored to working tree BEFORE verification
            # They're still there - we just need to restore CODE files from stash
            
            # Restore code files from stash (plan artifacts stay in working tree)
            if [[ -f /tmp/code_files.txt ]]; then
                local code_count=0
                while IFS= read -r file; do
                    [[ -n "$file" ]] || continue
                    git checkout "stash@{0}" -- "$file" 2>/dev/null || true
                    ((code_count++))
                done < /tmp/code_files.txt
                
                git stash drop "stash@{0}" 2>/dev/null || true
                log_info "Stash cleaned up ($code_count code files restored)"
            else
                # Fallback: try regular pop
                if git stash pop 2>/dev/null; then
                    log_info "Stash restored and removed"
                else
                    log_error "Stash restore failed (conflicts may exist)"
                    log_error "Manual fix: git stash list && git stash pop"
                fi
            fi
        fi
    fi
}

# Register cleanup for script EXIT (signals handled separately)
trap cleanup_stash EXIT

# Signal handler for interrupts (Ctrl+C, etc.)
signal_handler() {
    log_warn "Interrupted, cleaning up..."
    exit 1  # Triggers EXIT trap
}
trap signal_handler HUP INT TERM

# Expected plan artifacts (vectl-managed)
PLAN_ARTIFACTS=(
    "plan.yaml"
    "plan-dashboard.html"
)

# Common generated files to ignore
IGNORE_PATTERNS=(
    ".serena/"
    "coverage.xml"
    "*.pyc"
    "__pycache__/"
    ".pytest_cache/"
    ".mypy_cache/"
    "*.egg-info/"
)

usage() {
    cat <<EOF
Usage: $(basename "$0") [OPTIONS]

CLI Refactor Verification Protocol - Ensures deterministic verification by
automatically handling clean baseline for plan artifacts vs code changes.

Modes:
  --check              Run verification with clean baseline (default)
  --stash-only         Only stash changes for manual verification workflow
  --restore-only       Only restore stashed changes after manual work
  --dry-run            Show what would happen without executing

Options:
  --format <fmt>       Output format: text, json (default: text)
  -h, --help           Show this help

Environment Variables:
  SKIP_STASH           Skip stashing (assume clean baseline exists)
  KEEP_STASH           Keep stash after restore (for debugging)

Protocol Steps (--check mode):
  1. Identify dirty files (git status)
  2. Categorize: plan artifacts vs code changes
  3. Stash code changes (preserve plan artifacts)
  4. Run invar guard verification
  5. Restore stashed changes
  6. Report results

Exit Codes:
  0 - Success
  1 - Verification failed
  2 - Precondition error
  3 - Invalid arguments

Examples:
  # Standard verification workflow
  $(basename "$0") --check

  # Manual workflow: stash, verify, restore
  $(basename "$0") --stash-only
  # ... run manual checks ...
  $(basename "$0") --restore-only

  # JSON output for tooling
  $(basename "$0") --check --format json

  # Dry run (see what would happen)
  $(basename "$0") --check --dry-run
EOF
}

log_info() {
    if [[ "$OUTPUT_FORMAT" == "text" ]]; then
        echo -e "${GREEN}[INFO]${NC} $1"
    fi
}

log_warn() {
    if [[ "$OUTPUT_FORMAT" == "text" ]]; then
        echo -e "${YELLOW}[WARN]${NC} $1"
    fi
}

log_error() {
    if [[ "$OUTPUT_FORMAT" == "text" ]]; then
        echo -e "${RED}[ERROR]${NC} $1" >&2
    fi
}

log_step() {
    if [[ "$OUTPUT_FORMAT" == "text" ]]; then
        echo -e "${BLUE}[STEP]${NC} $1"
    fi
}

# Check if a file matches any pattern in IGNORE_PATTERNS
is_ignored_file() {
    local file="$1"
    local pattern
    
    for pattern in "${IGNORE_PATTERNS[@]}"; do
        # Convert glob pattern to regex
        local regex
        regex=$(echo "$pattern" | sed 's/\./\\./g' | sed 's/\*/.*/g' | sed 's/\?/./g')
        
        if [[ "$file" =~ $regex ]] || [[ "$file" == "$pattern" ]]; then
            return 0
        fi
    done
    
    return 1
}

# Check if a file is a plan artifact
is_plan_artifact() {
    local file="$1"
    
    for artifact in "${PLAN_ARTIFACTS[@]}"; do
        if [[ "$file" == "$artifact" ]] || [[ "$file" == *"$(basename "$artifact")" ]]; then
            return 0
        fi
    done
    
    return 1
}

# Get current git status
get_git_status() {
    git status --porcelain 2>/dev/null || echo ""
}

# Categorize dirty files
categorize_dirty_files() {
    local status
    status=$(get_git_status)
    
    local plan_files=()
    local code_files=()
    local ignored_files=()
    
    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        
        # Extract file path (after status codes)
        # Format: XY PATH or XY PATH -> PATH2 (for renames)
        local file
        file=$(echo "$line" | sed 's/^.. //' | sed 's/ -> / /' | awk '{print $1}')
        
        # Extract just the filename for comparison
        local basename
        basename=$(basename "$file")
        
        # Categorize
        if is_plan_artifact "$file"; then
            plan_files+=("$file")
        elif is_ignored_file "$file"; then
            ignored_files+=("$file")
        else
            code_files+=("$file")
        fi
    done <<< "$status"
    
    # Output categorization
    printf '%s\n' "${plan_files[@]}" > /tmp/plan_files.txt 2>/dev/null || true
    printf '%s\n' "${code_files[@]}" > /tmp/code_files.txt 2>/dev/null || true
    printf '%s\n' "${ignored_files[@]}" > /tmp/ignored_files.txt 2>/dev/null || true
    
    # Return counts
    echo "${#plan_files[@]} ${#code_files[@]} ${#ignored_files[@]}"
}

# Stash code changes only (preserve plan artifacts)
stash_code_changes() {
    log_step "Analyzing working tree..."
    
    local counts
    counts=$(categorize_dirty_files)
    
    local plan_count
    local code_count
    local ignored_count
    
    plan_count=$(echo "$counts" | awk '{print $1}')
    code_count=$(echo "$counts" | awk '{print $2}')
    ignored_count=$(echo "$counts" | awk '{print $3}')
    
    if [[ "$OUTPUT_FORMAT" == "json" ]]; then
        cat <<EOF
{
  "plan_artifacts": $plan_count,
  "code_changes": $code_count,
  "ignored_files": $ignored_count
}
EOF
    else
        log_info "Plan artifacts: $plan_count"
        log_info "Code changes: $code_count"
        log_info "Ignored files: $ignored_count"
    fi
    
    if [[ $code_count -eq 0 ]]; then
        log_info "No code changes to stash"
        return 0
    fi
    
    # Show what will be stashed
    if [[ "$DRY_RUN" == "true" ]]; then
        log_warn "[DRY RUN] Would stash:"
        while IFS= read -r file; do
            [[ -n "$file" ]] && log_warn "  - $file"
        done < /tmp/code_files.txt
        return 0
    fi
    
    log_step "Stashing code changes..."
    
    # Check for existing stash with our name
    if git stash list | grep -q "$STASH_NAME"; then
        log_error "Stash '$STASH_NAME' already exists"
        log_error "Run: git stash drop stash@{0} (if safe) or use --restore-only first"
        return 2
    fi
    
    # Build file list for partial stash
    local stash_args=()
    while IFS= read -r file; do
        [[ -n "$file" ]] && stash_args+=("$file")
    done < /tmp/code_files.txt
    
    if [[ ${#stash_args[@]} -eq 0 ]]; then
        log_info "No files to stash"
        return 0
    fi
    
    # ATOMICITY FIX: Backup plan artifacts before stashing
    # This ensures plan artifacts are NEVER hidden and can be reliably restored
    local backup_dir="/tmp/cli-refactor-backup-$$"
    mkdir -p "$backup_dir"
    
    if [[ $plan_count -gt 0 ]]; then
        log_info "Backing up plan artifacts..."
        while IFS= read -r artifact; do
            [[ -n "$artifact" ]] || continue
            if [[ -f "$artifact" ]]; then
                # Preserve directory structure
                local backup_path="$backup_dir/$artifact"
                mkdir -p "$(dirname "$backup_path")"
                cp "$artifact" "$backup_path"
                log_info "  Backed up: $artifact"
            fi
        done < /tmp/plan_files.txt
    fi
    
    # Create full stash (includes all changes)
    log_info "Creating stash..."
    if ! git stash push -m "$STASH_NAME" 2>/dev/null; then
        log_error "Failed to create stash"
        rm -rf "$backup_dir"
        return 2
    fi
    
    # Mark that we created a stash (for automatic cleanup)
    STASH_CREATED=true
    
    # Restore plan artifacts from backup
    if [[ $plan_count -gt 0 ]] && [[ -d "$backup_dir" ]]; then
        log_info "Restoring plan artifacts from backup..."
        while IFS= read -r artifact; do
            [[ -n "$artifact" ]] || continue
            local backup_file="$backup_dir/$artifact"
            if [[ -f "$backup_file" ]]; then
                # Restore to working tree
                cp "$backup_file" "$artifact"
                log_info "  Restored: $artifact"
            else
                log_warn "  Could not restore: $artifact (backup not found)"
            fi
        done < /tmp/plan_files.txt
        
        # Clean up backup
        rm -rf "$backup_dir"
    fi
    
    log_info "Code changes stashed as '$STASH_NAME'"
    
    # Show remaining dirty files
    local remaining
    remaining=$(get_git_status)
    if [[ -n "$remaining" ]]; then
        log_info "Remaining dirty files (plan artifacts):"
        while IFS= read -r line; do
            log_info "  $line"
        done <<< "$remaining"
    fi
    
    return 0
}

# Restore stashed changes
restore_stashed_changes() {
    # Check if stash exists
    if ! git stash list | grep -q "$STASH_NAME"; then
        log_info "No stash '$STASH_NAME' found"
        STASH_CREATED=false
        return 0
    fi
    
    if [[ "$DRY_RUN" == "true" ]]; then
        log_warn "[DRY RUN] Would restore stash: $STASH_NAME"
        return 0
    fi
    
    log_step "Restoring stashed changes..."
    
    # ATOMICITY: Selective restore to avoid plan artifact conflicts
    # We have plan artifacts in working tree from backup (after stash)
    # We have code changes in stash
    # We need to restore CODE files from stash, keep plan artifacts as-is
    
    # Restore code files from stash
    if [[ -f /tmp/code_files.txt ]]; then
        log_info "Restoring code changes from stash..."
        local has_code_files=false
        while IFS= read -r file; do
            [[ -n "$file" ]] || continue
            has_code_files=true
            if git checkout "stash@{0}" -- "$file" 2>/dev/null; then
                log_info "  Restored: $file"
            else
                log_warn "  Could not restore: $file (may be new/untracked)"
            fi
        done < /tmp/code_files.txt
        
        if [[ "$has_code_files" == "true" ]]; then
            # Clean up the stash since we've selectively restored
            git stash drop "stash@{0}"
            log_info "Stash removed after selective restore"
        fi
    else
        # Fallback: try regular pop if no code files list
        if ! git stash pop 2>/dev/null; then
            log_error "Failed to restore stash"
            log_error "Manual intervention required: git stash list && git stash pop"
            return 2
        fi
    fi
    
    # Plan artifacts are already in working tree (from backup in stash_code_changes)
    # No action needed - they survived the entire process
    
    # Clear state tracking since we successfully restored
    STASH_CREATED=false
    
    log_info "Code changes restored, plan artifacts preserved"
    return 0
}

# Run verification
run_verification() {
    log_step "Running verification..."
    
    cd "$PROJECT_ROOT"
    
    local cmd="uvx invar-tools guard --all"
    
    if [[ "$DRY_RUN" == "true" ]]; then
        log_warn "[DRY RUN] Would run: $cmd"
        return 0
    fi
    
    if [[ "$OUTPUT_FORMAT" == "json" ]]; then
        # Run and capture output
        local result
        set +e
        result=$($cmd 2>&1)
        local exit_code=$?
        set -e
        
        # Parse output
        local status
        [[ $exit_code -eq 0 ]] && status="pass" || status="fail"
        
        echo "{"
        echo "  \"status\": \"$status\","
        echo "  \"exit_code\": $exit_code,"
        echo "  \"output\": $(echo "$result" | jq -Rs '.')"
        echo "}"
        
        return $exit_code
    else
        # Run with visible output
        $cmd
        return $?
    fi
}

# Main workflow
main() {
    cd "$PROJECT_ROOT"
    
    case "$MODE" in
        check)
            # Standard verification workflow
            if [[ -n "${SKIP_STASH:-}" ]]; then
                log_info "Skipping stash (SKIP_STASH is set)"
            else
                if ! stash_code_changes; then
                    local stash_result=$?
                    log_error "Stash failed, aborting verification"
                    # Cleanup is automatic via trap
                    return $stash_result
                fi
            fi
            
            # Run verification (failure paths will auto-restore via trap)
            run_verification
            local verify_result=$?
            
            # EXPLICIT restore for success path (trap handles failure exits)
            # This ensures stash is cleaned up regardless of verification outcome
            if [[ -z "${SKIP_STASH:-}" ]] && [[ "$DRY_RUN" != "true" ]]; then
                if [[ $verify_result -eq 0 ]]; then
                    # Success: restore normally
                    restore_stashed_changes
                else
                    # Failure: restore automatically (already done by EXIT trap)
                    # Just clear state marker since trap will handle it
                    : # No action needed - trap fires on script exit
                fi
            fi
            
            if [[ $verify_result -eq 0 ]]; then
                log_info "✓ Verification passed"
            else
                log_error "✗ Verification failed"
                log_info "Stash will be restored automatically via cleanup"
            fi
            
            return $verify_result
            ;;
        stash-only)
            stash_code_changes
            return $?
            ;;
        restore-only)
            restore_stashed_changes
            return $?
            ;;
        *)
            log_error "Unknown mode: $MODE"
            usage
            return 3
            ;;
    esac
}

# Parse arguments
while [[ $# -gt 0 ]]; do
    case "$1" in
        --check)
            MODE="check"
            shift
            ;;
        --stash-only)
            MODE="stash-only"
            shift
            ;;
        --restore-only)
            MODE="restore-only"
            shift
            ;;
        --dry-run)
            DRY_RUN=true
            shift
            ;;
        --format)
            OUTPUT_FORMAT="$2"
            shift 2
            ;;
        -h|--help)
            usage
            exit 0
            ;;
        *)
            log_error "Unknown option: $1"
            usage
            exit 3
            ;;
    esac
done

# Set default mode
if [[ -z "$MODE" ]]; then
    MODE="check"
fi

# Run main
main