#!/usr/bin/env bash
set -euo pipefail

# ─────────────────────────────────────────────────────────────────────────────
# 17-code-promotion.sh — Environment Pipeline for SecureGit
#
# Promote code through environments (develop -> staging -> production)
# with security scan gates, promotion tags, backup before production,
# diff comparison, and rollback support.
#
# Usage:
#   ./17-code-promotion.sh                  # Launch interactive menu
#   ./17-code-promotion.sh status           # Show environment status
#   ./17-code-promotion.sh promote [env]    # Promote to next environment
#   ./17-code-promotion.sh diff [env1 env2] # Diff between environments
#   ./17-code-promotion.sh rollback [env]   # Rollback environment
#   ./17-code-promotion.sh history          # Show promotion history
# ─────────────────────────────────────────────────────────────────────────────

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
source "$SCRIPT_DIR/lib/securegit-common.sh"

sg_require_repo

# ─── Constants ───────────────────────────────────────────────────────────────
_REPO_ROOT="$(git rev-parse --show-toplevel)"
_PROMOTION_LOG="$_REPO_ROOT/.securegit/promotion.log"
_PROMOTION_TAG_PREFIX="promote"

# ─── Environment Mapping Helpers ─────────────────────────────────────────────

# Parse SG_ENVIRONMENTS into an ordered array
_env_list() {
    echo "$SG_ENVIRONMENTS"
}

# Get the branch for a given environment name
_env_branch() {
    local env_name="$1"
    local pair
    for pair in $SG_ENV_BRANCHES; do
        local key="${pair%%=*}"
        local val="${pair#*=}"
        if [[ "$key" == "$env_name" ]]; then
            echo "$val"
            return 0
        fi
    done
    sg_error "No branch mapping found for environment: $env_name"
    return 1
}

# Get the environment name for a given branch
_branch_env() {
    local branch="$1"
    local pair
    for pair in $SG_ENV_BRANCHES; do
        local key="${pair%%=*}"
        local val="${pair#*=}"
        if [[ "$val" == "$branch" ]]; then
            echo "$key"
            return 0
        fi
    done
    return 1
}

# Get the index of an environment in the ordered list (0-based)
_env_index() {
    local target="$1"
    local i=0
    local env
    for env in $SG_ENVIRONMENTS; do
        if [[ "$env" == "$target" ]]; then
            echo "$i"
            return 0
        fi
        (( i++ )) || true
    done
    return 1
}

# Get the environment at a given index
_env_at_index() {
    local target_idx="$1"
    local i=0
    local env
    for env in $SG_ENVIRONMENTS; do
        if (( i == target_idx )); then
            echo "$env"
            return 0
        fi
        (( i++ )) || true
    done
    return 1
}

# Get the total number of environments
_env_count() {
    local count=0
    local env
    for env in $SG_ENVIRONMENTS; do
        (( count++ )) || true
    done
    echo "$count"
}

# Get the next environment after the given one
_env_next() {
    local current="$1"
    local idx
    idx="$(_env_index "$current")" || { sg_error "Unknown environment: $current"; return 1; }
    local next_idx=$(( idx + 1 ))
    local total
    total="$(_env_count)"
    if (( next_idx >= total )); then
        return 1  # Already at the last environment
    fi
    _env_at_index "$next_idx"
}

# Get the previous environment before the given one
_env_prev() {
    local current="$1"
    local idx
    idx="$(_env_index "$current")" || { sg_error "Unknown environment: $current"; return 1; }
    if (( idx == 0 )); then
        return 1  # Already at the first environment
    fi
    _env_at_index $(( idx - 1 ))
}

# Check if an environment is the production (last) environment
_env_is_production() {
    local env_name="$1"
    local total
    total="$(_env_count)"
    local idx
    idx="$(_env_index "$env_name")" || return 1
    (( idx == total - 1 ))
}

# ─── Promotion Tag Helpers ───────────────────────────────────────────────────

# Generate a promotion tag name
_promotion_tag() {
    local from_env="$1"
    local to_env="$2"
    local timestamp
    timestamp="$(date +%Y%m%d-%H%M%S)"
    echo "${_PROMOTION_TAG_PREFIX}/${to_env}/${timestamp}"
}

# Get the latest promotion tag for an environment
_latest_promotion_tag() {
    local env_name="$1"
    git tag -l "${_PROMOTION_TAG_PREFIX}/${env_name}/*" --sort=-version:refname 2>/dev/null | head -1
}

# Get the previous promotion tag (second most recent) for an environment
_previous_promotion_tag() {
    local env_name="$1"
    git tag -l "${_PROMOTION_TAG_PREFIX}/${env_name}/*" --sort=-version:refname 2>/dev/null | sed -n '2p'
}

# Log a promotion event
_log_promotion() {
    local from_env="$1"
    local to_env="$2"
    local tag="$3"
    local commit="$4"
    local timestamp
    timestamp="$(date -Iseconds)"
    local user
    user="$(git config user.name 2>/dev/null || echo "$USER")"

    mkdir -p "$(dirname "$_PROMOTION_LOG")"
    printf "%s | %s | %s -> %s | tag: %s | commit: %s\n" \
        "$timestamp" "$user" "$from_env" "$to_env" "$tag" "$commit" \
        >> "$_PROMOTION_LOG"
}

# ─── Operations ──────────────────────────────────────────────────────────────

op_status() {
    sg_header "Environment Pipeline Status"

    # Fetch latest remote info if available
    if sg_has_remote; then
        sg_info "Fetching latest remote state..."
        _sg_cmd fetch --quiet "${SG_PRIMARY_REMOTE:-origin}" 2>/dev/null || true
    fi

    printf "  ${_C_BOLD}%-16s %-20s %-12s %-10s %s${_C_RESET}\n" \
        "ENVIRONMENT" "BRANCH" "COMMIT" "STATUS" "LAST PROMOTION"
    sg_divider

    local env
    for env in $SG_ENVIRONMENTS; do
        local branch
        branch="$(_env_branch "$env")" || continue

        local commit="(none)"
        local status="${_C_DIM}missing${_C_RESET}"
        local last_tag="-"

        if sg_branch_exists "$branch"; then
            commit="$(git rev-parse --short "$branch" 2>/dev/null || echo "?")"

            # Check if branch is clean relative to remote
            local remote_ref="refs/remotes/${SG_PRIMARY_REMOTE:-origin}/${branch}"
            if git show-ref --verify --quiet "$remote_ref" 2>/dev/null; then
                local ahead behind
                ahead="$(git rev-list --count "$remote_ref..$branch" 2>/dev/null || echo 0)"
                behind="$(git rev-list --count "$branch..$remote_ref" 2>/dev/null || echo 0)"
                if (( ahead == 0 && behind == 0 )); then
                    status="${_C_GREEN}synced${_C_RESET}"
                elif (( ahead > 0 && behind > 0 )); then
                    status="${_C_RED}diverged${_C_RESET}"
                elif (( ahead > 0 )); then
                    status="${_C_YELLOW}ahead +${ahead}${_C_RESET}"
                else
                    status="${_C_YELLOW}behind -${behind}${_C_RESET}"
                fi
            else
                status="${_C_CYAN}local only${_C_RESET}"
            fi

            # Get latest promotion tag
            local tag
            tag="$(_latest_promotion_tag "$env")"
            if [[ -n "$tag" ]]; then
                last_tag="$(echo "$tag" | sed "s|${_PROMOTION_TAG_PREFIX}/${env}/||")"
            fi
        fi

        printf "  %-16s %-20s %-12s " "$env" "$branch" "$commit"
        printf "${status}"
        printf "  %s\n" "$last_tag"
    done

    # Show commit difference between adjacent environments
    echo
    sg_info "Commit differences between environments:"

    local prev_env=""
    local prev_branch=""
    for env in $SG_ENVIRONMENTS; do
        local branch
        branch="$(_env_branch "$env")" || continue

        if [[ -n "$prev_env" ]] && sg_branch_exists "$branch" && sg_branch_exists "$prev_branch"; then
            local diff_count
            diff_count="$(git rev-list --count "$branch..$prev_branch" 2>/dev/null || echo "?")"
            printf "  %s (%s) -> %s (%s): ${_C_BOLD}%s${_C_RESET} commits pending\n" \
                "$prev_env" "$prev_branch" "$env" "$branch" "$diff_count"
        fi

        prev_env="$env"
        prev_branch="$branch"
    done
}

op_promote() {
    local target_env="${1:-}"

    sg_header "Code Promotion"

    # If no target given, figure out what to promote and where
    if [[ -z "$target_env" ]]; then
        local current_branch
        current_branch="$(sg_current_branch)"
        local current_env
        current_env="$(_branch_env "$current_branch" 2>/dev/null || true)"

        if [[ -n "$current_env" ]]; then
            local next_env
            next_env="$(_env_next "$current_env" 2>/dev/null || true)"
            if [[ -z "$next_env" ]]; then
                sg_warn "Current environment '$current_env' is the final stage. Nothing to promote to."
                return 0
            fi
            sg_info "Current branch '$current_branch' maps to environment '$current_env'."
            sg_info "Next environment: $next_env"
            echo
            if sg_confirm "Promote from $current_env to $next_env?"; then
                target_env="$next_env"
            else
                return 0
            fi
        else
            # Let user pick source and target
            sg_info "Current branch '$current_branch' is not mapped to an environment."
            echo
            sg_info "Available environments:"
            local env_arr=()
            local env
            for env in $SG_ENVIRONMENTS; do
                env_arr+=("$env")
            done

            local choice
            choice="$(sg_menu "Select target environment" "${env_arr[@]}")"
            target_env="${env_arr[$((choice - 1))]}"
        fi
    fi

    # Validate target environment
    _env_index "$target_env" >/dev/null || sg_die "Unknown environment: $target_env"

    local target_branch
    target_branch="$(_env_branch "$target_env")"

    # Get source environment (the one before target)
    local source_env
    source_env="$(_env_prev "$target_env" 2>/dev/null || true)"

    if [[ -z "$source_env" ]]; then
        sg_die "Cannot promote to '$target_env' - it is the first environment in the pipeline."
    fi

    local source_branch
    source_branch="$(_env_branch "$source_env")"

    # Validate branches exist
    sg_branch_exists "$source_branch" || sg_die "Source branch '$source_branch' does not exist."

    sg_info "Promotion: $source_env ($source_branch) -> $target_env ($target_branch)"

    # Show what will be promoted
    echo
    sg_info "Commits to be promoted:"
    if sg_branch_exists "$target_branch"; then
        local pending
        pending="$(git rev-list --count "$target_branch..$source_branch" 2>/dev/null || echo 0)"
        if (( pending == 0 )); then
            sg_warn "No new commits to promote. Environments are already in sync."
            return 0
        fi
        sg_info "$pending commit(s) pending promotion:"
        git log --oneline "$target_branch..$source_branch" 2>/dev/null | head -20
        local total_pending
        total_pending="$(git rev-list --count "$target_branch..$source_branch" 2>/dev/null || echo 0)"
        if (( total_pending > 20 )); then
            sg_info "  ... and $((total_pending - 20)) more"
        fi
    else
        sg_info "Target branch '$target_branch' does not exist yet. It will be created."
        git log --oneline "$source_branch" 2>/dev/null | head -20
    fi

    # Security scan gate
    echo
    sg_info "Running security scan gate..."
    if _sg_cmd scan --min-severity medium "$_REPO_ROOT" 2>/dev/null; then
        sg_success "Security scan passed."
    else
        local scan_exit=$?
        sg_warn "Security scan found issues."
        if ! sg_confirm "Continue promotion despite scan findings?" "n"; then
            sg_error "Promotion aborted due to security findings."
            return 1
        fi
    fi

    # Production deployment confirmation gate
    if _env_is_production "$target_env"; then
        echo
        printf "${_C_BOLD}${_C_RED}━━━ PRODUCTION DEPLOYMENT GATE ━━━${_C_RESET}\n"
        sg_warn "You are about to promote to PRODUCTION ($target_branch)."
        echo
        if ! sg_confirm "Are you sure you want to promote to production?" "n"; then
            sg_info "Production promotion cancelled."
            return 0
        fi
        # Double confirmation for production
        local confirm_text
        confirm_text="$(sg_prompt "Type the environment name to confirm" "")"
        if [[ "$confirm_text" != "$target_env" ]]; then
            sg_error "Confirmation text did not match. Expected: $target_env"
            return 1
        fi

        # Backup before production promotion
        echo
        sg_info "Creating backup before production promotion..."
        if _sg_cmd backup push 2>/dev/null; then
            sg_success "Backup completed."
        else
            sg_warn "Backup not configured or failed. Proceeding without backup."
        fi
    fi

    # Ensure working tree is clean
    sg_require_clean

    # Perform the promotion
    echo
    sg_info "Performing promotion..."

    local source_commit
    source_commit="$(git rev-parse --short "$source_branch")"

    if sg_branch_exists "$target_branch"; then
        # Merge source into target
        _sg_cmd checkout "$target_branch" 2>/dev/null || git checkout "$target_branch"
        if ! _sg_cmd merge "$source_branch" 2>/dev/null && ! git merge "$source_branch" --no-edit; then
            sg_error "Merge conflicts detected. Resolve conflicts and try again."
            sg_info "To abort: git merge --abort"
            return 1
        fi
    else
        # Create target branch from source
        _sg_cmd checkout -b "$target_branch" "$source_branch" 2>/dev/null \
            || git checkout -b "$target_branch" "$source_branch"
    fi

    # Create promotion tag with metadata
    local tag_name
    tag_name="$(_promotion_tag "$source_env" "$target_env")"
    local tag_message
    tag_message="$(printf "Promotion: %s -> %s\nSource: %s (%s)\nCommit: %s\nDate: %s\nUser: %s" \
        "$source_env" "$target_env" \
        "$source_branch" "$source_env" \
        "$source_commit" \
        "$(date -Iseconds)" \
        "$(git config user.name 2>/dev/null || echo "$USER")")"

    git tag -a "$tag_name" -m "$tag_message"
    sg_success "Created promotion tag: $tag_name"

    # Log the promotion
    _log_promotion "$source_env" "$target_env" "$tag_name" "$source_commit"

    # Push if remote exists
    if sg_has_remote; then
        echo
        if sg_confirm "Push promotion to remote?"; then
            local remote="${SG_PRIMARY_REMOTE:-origin}"
            git push "$remote" "$target_branch" 2>/dev/null || true
            git push "$remote" "$tag_name" 2>/dev/null || true
            sg_success "Pushed to remote."
        fi
    fi

    echo
    sg_success "Promotion complete: $source_env -> $target_env"
    sg_info "Tag: $tag_name"
    sg_info "Commit: $source_commit"
    sg_undo_available
}

op_diff() {
    local env1="${1:-}" env2="${2:-}"

    sg_header "Environment Diff"

    local env_arr=()
    local env
    for env in $SG_ENVIRONMENTS; do
        env_arr+=("$env")
    done

    if [[ -z "$env1" ]]; then
        local choice
        choice="$(sg_menu "Select first environment" "${env_arr[@]}")"
        env1="${env_arr[$((choice - 1))]}"
    fi

    if [[ -z "$env2" ]]; then
        local choice
        choice="$(sg_menu "Select second environment" "${env_arr[@]}")"
        env2="${env_arr[$((choice - 1))]}"
    fi

    local branch1 branch2
    branch1="$(_env_branch "$env1")"
    branch2="$(_env_branch "$env2")"

    sg_branch_exists "$branch1" || sg_die "Branch '$branch1' ($env1) does not exist."
    sg_branch_exists "$branch2" || sg_die "Branch '$branch2' ($env2) does not exist."

    sg_info "Comparing: $env1 ($branch1) vs $env2 ($branch2)"
    echo

    # Show commit difference
    sg_info "Commits in $env1 not in $env2:"
    local count1
    count1="$(git rev-list --count "$branch2..$branch1" 2>/dev/null || echo 0)"
    if (( count1 == 0 )); then
        sg_info "  (none)"
    else
        git log --oneline "$branch2..$branch1" 2>/dev/null | head -30
        if (( count1 > 30 )); then
            sg_info "  ... and $((count1 - 30)) more"
        fi
    fi

    echo
    sg_info "Commits in $env2 not in $env1:"
    local count2
    count2="$(git rev-list --count "$branch1..$branch2" 2>/dev/null || echo 0)"
    if (( count2 == 0 )); then
        sg_info "  (none)"
    else
        git log --oneline "$branch1..$branch2" 2>/dev/null | head -30
        if (( count2 > 30 )); then
            sg_info "  ... and $((count2 - 30)) more"
        fi
    fi

    echo
    if sg_confirm "Show file-level diff?"; then
        sg_divider
        git diff --stat "$branch2..$branch1" 2>/dev/null
        echo
        if sg_confirm "Show full diff?"; then
            git diff "$branch2..$branch1" 2>/dev/null | head -500
            local total_lines
            total_lines="$(git diff "$branch2..$branch1" 2>/dev/null | wc -l)"
            if (( total_lines > 500 )); then
                sg_info "... ($((total_lines - 500)) more lines truncated)"
            fi
        fi
    fi
}

op_rollback() {
    local target_env="${1:-}"

    sg_header "Environment Rollback"

    if [[ -z "$target_env" ]]; then
        local env_arr=()
        local env
        for env in $SG_ENVIRONMENTS; do
            env_arr+=("$env")
        done

        local choice
        choice="$(sg_menu "Select environment to rollback" "${env_arr[@]}")"
        target_env="${env_arr[$((choice - 1))]}"
    fi

    _env_index "$target_env" >/dev/null || sg_die "Unknown environment: $target_env"

    local target_branch
    target_branch="$(_env_branch "$target_env")"
    sg_branch_exists "$target_branch" || sg_die "Branch '$target_branch' does not exist."

    # Find the previous promotion tag
    local current_tag previous_tag
    current_tag="$(_latest_promotion_tag "$target_env")"
    previous_tag="$(_previous_promotion_tag "$target_env")"

    if [[ -z "$previous_tag" ]]; then
        sg_warn "No previous promotion tag found for '$target_env'."
        sg_info "Available promotion tags for $target_env:"
        git tag -l "${_PROMOTION_TAG_PREFIX}/${target_env}/*" --sort=-version:refname 2>/dev/null \
            | head -10 || sg_info "  (none)"
        return 1
    fi

    sg_info "Environment:     $target_env ($target_branch)"
    sg_info "Current tag:     ${current_tag:-none}"
    sg_info "Rollback to tag: $previous_tag"
    echo

    # Show what will change
    local prev_commit
    prev_commit="$(git rev-parse --short "$previous_tag" 2>/dev/null)"
    local curr_commit
    curr_commit="$(git rev-parse --short "$target_branch" 2>/dev/null)"

    sg_info "Rolling back from $curr_commit to $prev_commit"
    sg_info "Commits to be reverted:"
    git log --oneline "$previous_tag..$target_branch" 2>/dev/null | head -20
    echo

    # Production rollback gets extra warning
    if _env_is_production "$target_env"; then
        printf "${_C_BOLD}${_C_RED}━━━ PRODUCTION ROLLBACK ━━━${_C_RESET}\n"
        sg_warn "You are about to rollback PRODUCTION."
    fi

    if ! sg_confirm "Proceed with rollback?" "n"; then
        sg_info "Rollback cancelled."
        return 0
    fi

    sg_require_clean

    # Perform rollback by resetting branch to previous tag
    _sg_cmd checkout "$target_branch" 2>/dev/null || git checkout "$target_branch"
    git reset --hard "$previous_tag"

    # Create a rollback tag
    local timestamp
    timestamp="$(date +%Y%m%d-%H%M%S)"
    local rollback_tag="${_PROMOTION_TAG_PREFIX}/${target_env}/rollback-${timestamp}"
    local rollback_msg
    rollback_msg="$(printf "Rollback: %s\nFrom: %s (%s)\nTo: %s (%s)\nDate: %s\nUser: %s" \
        "$target_env" \
        "${current_tag:-$curr_commit}" "$curr_commit" \
        "$previous_tag" "$prev_commit" \
        "$(date -Iseconds)" \
        "$(git config user.name 2>/dev/null || echo "$USER")")"

    git tag -a "$rollback_tag" -m "$rollback_msg"

    # Log the rollback
    mkdir -p "$(dirname "$_PROMOTION_LOG")"
    printf "%s | %s | ROLLBACK %s | from: %s to: %s | tag: %s\n" \
        "$(date -Iseconds)" \
        "$(git config user.name 2>/dev/null || echo "$USER")" \
        "$target_env" \
        "${current_tag:-$curr_commit}" "$previous_tag" \
        "$rollback_tag" \
        >> "$_PROMOTION_LOG"

    # Push if remote exists
    if sg_has_remote; then
        echo
        if sg_confirm "Force push rollback to remote? (required to sync)" "n"; then
            local remote="${SG_PRIMARY_REMOTE:-origin}"
            git push --force-with-lease "$remote" "$target_branch" 2>/dev/null || true
            git push "$remote" "$rollback_tag" 2>/dev/null || true
            sg_success "Pushed rollback to remote."
        else
            sg_warn "Remote not updated. Push manually with: git push --force-with-lease ${SG_PRIMARY_REMOTE:-origin} $target_branch"
        fi
    fi

    echo
    sg_success "Rollback complete: $target_env reset to $previous_tag"
    sg_info "Rollback tag: $rollback_tag"
    sg_undo_available
}

op_history() {
    sg_header "Promotion History"

    if [[ ! -f "$_PROMOTION_LOG" ]]; then
        sg_info "No promotion history found."
        sg_info "Promotions will be logged to: $_PROMOTION_LOG"
        echo

        # Also show any promotion tags
        sg_info "Promotion tags in repository:"
        local tags
        tags="$(git tag -l "${_PROMOTION_TAG_PREFIX}/*" --sort=-version:refname 2>/dev/null | head -20)"
        if [[ -z "$tags" ]]; then
            sg_info "  (none)"
        else
            echo "$tags" | while IFS= read -r tag; do
                local tag_date
                tag_date="$(git log -1 --format='%ci' "$tag" 2>/dev/null | cut -d' ' -f1,2)"
                printf "  ${_C_CYAN}%s${_C_RESET}  ${_C_DIM}%s${_C_RESET}\n" "$tag" "$tag_date"
            done
        fi
        return 0
    fi

    printf "  ${_C_BOLD}%-22s %-14s %-28s %-20s %s${_C_RESET}\n" \
        "TIMESTAMP" "USER" "PROMOTION" "TAG" "COMMIT"
    sg_divider

    # Show most recent first
    tac "$_PROMOTION_LOG" 2>/dev/null | head -30 | while IFS='|' read -r timestamp user promotion tag commit; do
        timestamp="$(echo "$timestamp" | xargs)"
        user="$(echo "$user" | xargs)"
        promotion="$(echo "$promotion" | xargs)"
        tag="$(echo "$tag" | xargs | sed 's/^tag: //')"
        commit="$(echo "$commit" | xargs | sed 's/^commit: //')"

        printf "  %-22s %-14s %-28s %-20s %s\n" \
            "$timestamp" "$user" "$promotion" "$tag" "$commit"
    done

    local total
    total="$(wc -l < "$_PROMOTION_LOG" | tr -d ' ')"
    echo
    sg_info "Total promotions: $total"

    if (( total > 30 )); then
        sg_info "(Showing most recent 30)"
    fi
}

# ─── CLI Shortcut Handling ───────────────────────────────────────────────────

case "${1:-}" in
    status)
        op_status
        exit 0
        ;;
    promote)
        shift
        op_promote "${1:-}"
        exit 0
        ;;
    diff)
        shift
        op_diff "${1:-}" "${2:-}"
        exit 0
        ;;
    rollback)
        shift
        op_rollback "${1:-}"
        exit 0
        ;;
    history)
        op_history
        exit 0
        ;;
    "")
        ;; # Fall through to interactive menu
    *)
        sg_die "Unknown command: $1. Use: status, promote, diff, rollback, history"
        ;;
esac

# ─── Interactive Menu Loop ───────────────────────────────────────────────────

while true; do
    choice="$(sg_menu "SecureGit Code Promotion" \
        "Show environment status" \
        "Promote to next environment" \
        "Diff between environments" \
        "Rollback environment" \
        "Promotion history" \
        "Quit"
    )"

    case "$choice" in
        1) op_status ;;
        2) op_promote "" ;;
        3) op_diff "" "" ;;
        4) op_rollback "" ;;
        5) op_history ;;
        6) sg_info "Goodbye."; exit 0 ;;
    esac

    echo
    sg_divider
done
