#!/bin/bash
# Lightweight sync script for generating patches
# Called by CoderFlow server's sync-repos endpoint
# This is optimized for speed - no unnecessary initialization

set -o pipefail

TASK_OUTPUT_DIR="${TASK_OUTPUT_DIR:-/task-output}"
WORKSPACE_DIR="${WORKSPACE_DIR:-/workspace}"
patches_dir="$TASK_OUTPUT_DIR/patches"
TURN_NUMBER="${TURN_NUMBER:-}"

# Simple log function
log() {
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" >&2
}

# Create patches directory and temp directory for parallel results
mkdir -p "$patches_dir"
temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT

# Process a single repository (designed to run in parallel)
process_repo() {
    local repo_name="$1"
    local repo_path="$2"
    local output_file="$3"
    local repo_read_only="${4:-false}"
    local ignore_lockfile="${IGNORE_LOCKFILE_CHANGES:-false}"

    cd "$repo_path" || return 1

    # Capture current branch name ("HEAD" means detached HEAD state)
    local current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
    if [ -z "$current_branch" ] || [ "$current_branch" = "HEAD" ]; then
        current_branch=""
    fi

    # Quick check: any changes at all?
    local status_output=$(git status --porcelain 2>/dev/null)
    if [ -z "$status_output" ]; then
        return 1
    fi

    log "Changes detected in $repo_name"

    # Stage all changes
    git add -A

    # Generate patch
    local patch_file="$repo_name.patch"
    if [ "$ignore_lockfile" = "true" ]; then
        git diff --cached -- ':!package-lock.json' ':!**/package-lock.json' > "$patches_dir/$patch_file"
    else
        git diff --cached > "$patches_dir/$patch_file"
    fi

    # Check if patch has content
    if [ ! -s "$patches_dir/$patch_file" ]; then
        rm -f "$patches_dir/$patch_file"
        return 1
    fi

    # Get stats using git diff --stat (fast)
    local stat_output
    if [ "$ignore_lockfile" = "true" ]; then
        stat_output=$(git diff --cached --stat -- ':!package-lock.json' ':!**/package-lock.json' 2>/dev/null | tail -1)
    else
        stat_output=$(git diff --cached --stat 2>/dev/null | tail -1)
    fi

    local files_changed=0 lines_added=0 lines_deleted=0
    if [ -n "$stat_output" ]; then
        files_changed=$(echo "$stat_output" | grep -oE '[0-9]+ file' | grep -oE '[0-9]+' || echo "0")
        lines_added=$(echo "$stat_output" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo "0")
        lines_deleted=$(echo "$stat_output" | grep -oE '[0-9]+ deletion' | grep -oE '[0-9]+' || echo "0")
    fi
    files_changed=${files_changed:-0}
    lines_added=${lines_added:-0}
    lines_deleted=${lines_deleted:-0}

    # Get file list
    local files_list
    if [ "$ignore_lockfile" = "true" ]; then
        files_list=$(git diff --cached --name-only -- ':!package-lock.json' ':!**/package-lock.json' 2>/dev/null)
    else
        files_list=$(git diff --cached --name-only 2>/dev/null)
    fi
    # Build files JSON via stdin to avoid "Argument list too long" on large imports.
    local files_detail_file="$output_file.files"
    echo "$files_list" | jq -R -s -c 'split("\n") | map(select(length > 0)) | map({path: ., added: 0, deleted: 0})' > "$files_detail_file"
    [ ! -s "$files_detail_file" ] && echo '[]' > "$files_detail_file"

    # Detect upstream divergence for the current branch when tracking is configured.
    local upstream_ref
    upstream_ref=$(git rev-parse --abbrev-ref --symbolic-full-name @{upstream} 2>/dev/null || true)
    local upstream_status="no-upstream"
    local upstream_behind=0
    local upstream_ahead=0

    if [ -n "$upstream_ref" ] && [ "$upstream_ref" != "@{upstream}" ]; then
        upstream_status="up-to-date"
        local upstream_remote="${upstream_ref%%/*}"

        if [ -n "$upstream_remote" ]; then
            git fetch "$upstream_remote" --quiet 2>/dev/null || true
        fi

        upstream_behind=$(git rev-list --count HEAD.."$upstream_ref" 2>/dev/null || echo "0")
        upstream_ahead=$(git rev-list --count "$upstream_ref"..HEAD 2>/dev/null || echo "0")
        upstream_behind=${upstream_behind:-0}
        upstream_ahead=${upstream_ahead:-0}

        if [ "$upstream_behind" -gt 0 ] && [ "$upstream_ahead" -gt 0 ]; then
            upstream_status="diverged"
        elif [ "$upstream_behind" -gt 0 ]; then
            upstream_status="behind"
        elif [ "$upstream_ahead" -gt 0 ]; then
            upstream_status="ahead"
        fi
    fi

    local read_only_json="false"
    local policy_violation=""
    local policy_message=""

    if [ "$repo_read_only" = "true" ]; then
        read_only_json="true"
        policy_violation="read_only_repo_modified"
        policy_message="Repository \"$repo_name\" is configured as read-only for this environment. Discard changes under $repo_path before approval."
    fi

    # Pipe files JSON via stdin (--slurpfile) to avoid exceeding ARG_MAX
    # with large file lists (e.g. 8000+ imported source members).
    jq -n \
        --arg name "$repo_name" \
        --arg path "$repo_path" \
        --arg branch "$current_branch" \
        --arg upstream_status "$upstream_status" \
        --arg patch_file "$patch_file" \
        --arg policy_violation "$policy_violation" \
        --arg policy_message "$policy_message" \
        --argjson upstream_behind "${upstream_behind:-0}" \
        --argjson upstream_ahead "${upstream_ahead:-0}" \
        --argjson files_changed "${files_changed:-0}" \
        --argjson lines_added "${lines_added:-0}" \
        --argjson lines_deleted "${lines_deleted:-0}" \
        --slurpfile files_arr "$files_detail_file" \
        --argjson read_only "$read_only_json" \
        '{
            name: $name,
            path: $path,
            branch: $branch,
            upstream_status: $upstream_status,
            upstream_behind: $upstream_behind,
            upstream_ahead: $upstream_ahead,
            files_changed: $files_changed,
            lines_added: $lines_added,
            lines_deleted: $lines_deleted,
            patch_file: $patch_file,
            files: $files_arr[0],
            read_only: $read_only
        } + (if $policy_violation != "" then {
            policy_violation: $policy_violation,
            policy_message: $policy_message
        } else {} end)' > "$output_file"
    rm -f "$files_detail_file"
}

# Export for subshells
export -f process_repo log
export TASK_OUTPUT_DIR WORKSPACE_DIR patches_dir IGNORE_LOCKFILE_CHANGES

pids=()
repo_index=0

# Process repos from REPOS_CONFIG or scan directories
if [ -n "$REPOS_CONFIG" ]; then
    repo_count=$(echo "$REPOS_CONFIG" | jq 'length' 2>/dev/null)
    if [ -n "$repo_count" ] && [ "$repo_count" -gt 0 ]; then
        for index in $(seq 0 $((repo_count - 1))); do
            repo_json=$(echo "$REPOS_CONFIG" | jq -c ".[$index]" 2>/dev/null)
            [ -z "$repo_json" ] || [ "$repo_json" = "null" ] && continue

            repo_name=$(echo "$repo_json" | jq -r 'if type == "object" then (.name // empty) else . end' 2>/dev/null)
            repo_path_rel=$(echo "$repo_json" | jq -r 'if type == "object" then (.path // empty) else empty end' 2>/dev/null)
            repo_read_only=$(echo "$repo_json" | jq -r 'if type == "object" then ((.read_only // false) | if . then "true" else "false" end) else "false" end' 2>/dev/null)
            [ -z "$repo_name" ] || [ "$repo_name" = "null" ] && continue

            if [ -n "$repo_path_rel" ] && [ "$repo_path_rel" != "null" ]; then
                repo_path="$WORKSPACE_DIR/$repo_path_rel"
            else
                repo_path="$WORKSPACE_DIR/$repo_name"
            fi

            [ ! -d "$repo_path/.git" ] && continue

            # Launch in background
            output_file="$temp_dir/repo_$repo_index.json"
            process_repo "$repo_name" "$repo_path" "$output_file" "$repo_read_only" &
            pids+=($!)
            repo_index=$((repo_index + 1))
        done
    fi
else
    for repo_path in "$WORKSPACE_DIR"/*; do
        if [ -d "$repo_path/.git" ]; then
            repo_name=$(basename "$repo_path")
            output_file="$temp_dir/repo_$repo_index.json"
            process_repo "$repo_name" "$repo_path" "$output_file" "false" &
            pids+=($!)
            repo_index=$((repo_index + 1))
        fi
    done
fi

# Wait for all background jobs
for pid in "${pids[@]}"; do
    wait $pid 2>/dev/null || true
done

# Collect results into JSON array
result="["
first=true
for f in "$temp_dir"/repo_*.json; do
    [ -f "$f" ] && [ -s "$f" ] || continue
    if $first; then
        first=false
    else
        result="$result,"
    fi
    result="$result$(cat "$f")"
done
result="$result]"

# Optional per-turn snapshot copy for rewind history
turn_number_valid=false
if [[ "$TURN_NUMBER" =~ ^[0-9]+$ ]]; then
    turn_number_valid=true
fi

if $turn_number_valid; then
    turn_dir="$patches_dir/turn-$TURN_NUMBER"
    mkdir -p "$turn_dir"
    find "$turn_dir" -maxdepth 1 -type f -name '*.patch' -delete 2>/dev/null || true

    while IFS= read -r patch_file; do
        [ -z "$patch_file" ] && continue
        [ -f "$patches_dir/$patch_file" ] || continue
        cp "$patches_dir/$patch_file" "$turn_dir/$patch_file"
    done < <(echo "$result" | jq -r '.[]?.patch_file' 2>/dev/null)
fi

# Update task.json with repos_changed and stats (create if it doesn't exist)
# This ensures sync results are readable by the server even for staged/running tasks
existing_json='{}'
if [ -f "$TASK_OUTPUT_DIR/task.json" ]; then
    existing_json=$(cat "$TASK_OUTPUT_DIR/task.json")
fi

turn_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
if $turn_number_valid; then
    echo "$existing_json" | jq --argjson repos "$result" --argjson turn "$TURN_NUMBER" --arg timestamp "$turn_timestamp" '
        .repos_changed = $repos |
        .stats = {
            repositories: ($repos | length),
            filesChanged: ([$repos[].files_changed] | add // 0),
            linesAdded: ([$repos[].lines_added] | add // 0),
            linesDeleted: ([$repos[].lines_deleted] | add // 0)
        } |
        .turn_patches = (
            ((.turn_patches // []) | map(select((.turn // -1) != $turn))) + [{
                turn: $turn,
                repos_changed: ($repos | map(.name)),
                stats: {
                    repositories: ($repos | length),
                    filesChanged: ([$repos[].files_changed] | add // 0),
                    linesAdded: ([$repos[].lines_added] | add // 0),
                    linesDeleted: ([$repos[].lines_deleted] | add // 0)
                },
                timestamp: $timestamp
            }] | sort_by(.turn)
        )
    ' > "$TASK_OUTPUT_DIR/task.json.tmp" && \
    mv "$TASK_OUTPUT_DIR/task.json.tmp" "$TASK_OUTPUT_DIR/task.json"
else
    echo "$existing_json" | jq --argjson repos "$result" '
        .repos_changed = $repos |
        .stats = {
            repositories: ($repos | length),
            filesChanged: ([$repos[].files_changed] | add // 0),
            linesAdded: ([$repos[].lines_added] | add // 0),
            linesDeleted: ([$repos[].lines_deleted] | add // 0)
        }
    ' > "$TASK_OUTPUT_DIR/task.json.tmp" && \
    mv "$TASK_OUTPUT_DIR/task.json.tmp" "$TASK_OUTPUT_DIR/task.json"
fi

# Output the result
echo "$result"
