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

# ─────────────────────────────────────────────────────────────────────────────
# 02-branch-manager.sh — Branch Lifecycle Manager for SecureGit
#
# Full branch lifecycle: create, list, switch, delete, rename, track/untrack,
# stale detection, and config-driven naming with prefix conventions.
#
# Usage:
#   ./02-branch-manager.sh                  # Launch interactive menu
#   ./02-branch-manager.sh create [name]    # Quick create
#   ./02-branch-manager.sh list             # List branches
#   ./02-branch-manager.sh switch [name]    # Quick switch
#   ./02-branch-manager.sh stale            # Show stale branches
# ─────────────────────────────────────────────────────────────────────────────

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

sg_require_repo

# ─── Constants ───────────────────────────────────────────────────────────────
_REPO_ROOT="$(git rev-parse --show-toplevel)"
_STALE_DAYS="${SG_STALE_BRANCH_DAYS:-30}"
_REMOTE="${SG_PRIMARY_REMOTE:-origin}"

# ─── Helpers ─────────────────────────────────────────────────────────────────

# Format a branch name: lowercase, hyphens, strip invalid chars
_format_branch_name() {
    local raw="$1"
    echo "$raw" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]/-' '-' | sed 's/^-//;s/-$//'
}

# Convert a freeform description into a branch-name slug
_slugify() {
    local text="$1"
    echo "$text" | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//' | head -c 60
}

# Get branch age in human-readable format
_branch_age() {
    local branch="$1"
    git log -1 --format='%cr' "$branch" 2>/dev/null || echo "unknown"
}

# Get branch age in days (for stale detection)
_branch_age_days() {
    local branch="$1"
    local epoch_now epoch_branch
    epoch_now="$(date +%s)"
    epoch_branch="$(git log -1 --format='%ct' "$branch" 2>/dev/null || echo "$epoch_now")"
    echo $(( (epoch_now - epoch_branch) / 86400 ))
}

# Check if branch has a remote tracking branch
_has_tracking() {
    local branch="$1"
    git config --get "branch.${branch}.remote" &>/dev/null
}

# Get tracking info for a branch
_tracking_info() {
    local branch="$1"
    local remote_ref
    remote_ref="$(git config --get "branch.${branch}.merge" 2>/dev/null || true)"
    local remote
    remote="$(git config --get "branch.${branch}.remote" 2>/dev/null || true)"

    if [[ -n "$remote_ref" ]] && [[ -n "$remote" ]]; then
        local short="${remote_ref#refs/heads/}"
        # Check ahead/behind
        local ahead behind
        ahead="$(git rev-list --count "${remote}/${short}..${branch}" 2>/dev/null || echo 0)"
        behind="$(git rev-list --count "${branch}..${remote}/${short}" 2>/dev/null || echo 0)"

        local status_parts=()
        (( ahead > 0 )) && status_parts+=("${ahead} ahead")
        (( behind > 0 )) && status_parts+=("${behind} behind")

        if (( ${#status_parts[@]} > 0 )); then
            echo "${remote}/${short} ($(IFS=', '; echo "${status_parts[*]}"))"
        else
            echo "${remote}/${short} (up to date)"
        fi
    else
        echo "no tracking"
    fi
}

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

op_create() {
    sg_header "Create Branch"

    local current
    current="$(sg_current_branch)"
    sg_info "Current branch: $current"
    echo

    # Select prefix
    local prefixes
    IFS=' ' read -ra prefixes <<< "$(sg_branch_prefixes)"

    sg_info "Branch type prefixes:"
    local i
    for i in "${!prefixes[@]}"; do
        printf "  ${_C_BOLD}%d)${_C_RESET} %s\n" "$((i + 1))" "${prefixes[$i]}"
    done
    printf "  ${_C_BOLD}%d)${_C_RESET} %s\n" "$((${#prefixes[@]} + 1))" "none (custom)"
    echo

    local prefix_choice
    while true; do
        printf "Select type [1-%d]: " "$(( ${#prefixes[@]} + 1 ))"
        read -r prefix_choice
        if [[ "$prefix_choice" =~ ^[0-9]+$ ]] && (( prefix_choice >= 1 && prefix_choice <= ${#prefixes[@]} + 1 )); then
            break
        fi
        sg_warn "Invalid selection."
    done

    local prefix=""
    if (( prefix_choice <= ${#prefixes[@]} )); then
        prefix="${prefixes[$((prefix_choice - 1))]}"
    fi

    # Get description or ticket
    local description
    description="$(sg_prompt "Branch description or ticket ID")"

    if [[ -z "$description" ]]; then
        sg_error "Description cannot be empty."
        return 1
    fi

    # Auto-suggest branch name
    local slug suggested
    slug="$(_slugify "$description")"

    if [[ -n "$prefix" ]]; then
        suggested="${prefix}/${slug}"
    else
        suggested="$slug"
    fi

    sg_info "Suggested name: $suggested"
    local final_name
    final_name="$(sg_prompt "Branch name" "$suggested")"
    final_name="$(_format_branch_name "$final_name")"

    # Validate
    if sg_branch_exists "$final_name"; then
        sg_error "Branch '$final_name' already exists."
        return 1
    fi

    # Select base
    local default_base
    default_base="$(sg_default_branch)"
    local base
    base="$(sg_prompt "Base branch" "$default_base")"

    if ! sg_branch_exists "$base" && ! git show-ref --verify --quiet "refs/remotes/${_REMOTE}/${base}" 2>/dev/null; then
        sg_error "Base branch '$base' does not exist."
        return 1
    fi

    # Create snapshot before branching
    sg_snapshot_create "pre-branch-create: $final_name"

    # Create and switch
    _sg_cmd checkout -b "$final_name" "$base" 2>/dev/null \
        || git checkout -b "$final_name" "$base"

    sg_success "Created and switched to branch: $final_name"
    sg_info "Based on: $base"

    # Offer to push and set upstream
    if sg_has_remote; then
        if sg_confirm "Push and set upstream tracking?"; then
            git push -u "$_REMOTE" "$final_name"
            sg_success "Pushed with upstream: ${_REMOTE}/$final_name"
        fi
    fi

    sg_undo_available
}

op_list() {
    sg_header "Branch List"

    local current
    current="$(sg_current_branch)"

    printf "  ${_C_BOLD}%-3s %-35s %-16s %s${_C_RESET}\n" "" "BRANCH" "LAST COMMIT" "TRACKING"
    sg_divider

    while IFS= read -r branch; do
        branch="${branch#\* }"
        branch="${branch## }"
        branch="$(echo "$branch" | xargs)"  # trim whitespace

        [[ -z "$branch" ]] && continue

        local marker=" "
        local color=""
        if [[ "$branch" == "$current" ]]; then
            marker="*"
            color="$_C_GREEN"
        elif sg_is_protected "$branch"; then
            color="$_C_YELLOW"
        fi

        local age tracking
        age="$(_branch_age "$branch")"
        tracking="$(_tracking_info "$branch")"

        printf "  ${color}${_C_BOLD}%-3s${_C_RESET}${color}%-35s${_C_RESET} ${_C_DIM}%-16s${_C_RESET} ${_C_DIM}%s${_C_RESET}\n" \
            "$marker" "$branch" "$age" "$tracking"
    done < <(git branch --format='%(refname:short)' 2>/dev/null)

    echo
    local total
    total="$(git branch --format='%(refname:short)' | wc -l | tr -d ' ')"
    sg_info "Total: $total branch(es) (* = current, yellow = protected)"
}

op_switch() {
    sg_header "Switch Branch"

    local current
    current="$(sg_current_branch)"
    sg_info "Current branch: $current"
    echo

    # Check for uncommitted changes
    local status
    status="$(git status --porcelain 2>/dev/null)"
    if [[ -n "$status" ]]; then
        sg_warn "You have uncommitted changes:"
        git status --short
        echo

        local action
        action="$(sg_menu "Handle uncommitted changes" \
            "Stash changes before switching" \
            "Continue anyway (changes carry over)" \
            "Cancel"
        )"

        case "$action" in
            1)
                git stash push -m "auto-stash before switching from $current" --include-untracked
                sg_success "Changes stashed."
                ;;
            2) sg_warn "Proceeding with uncommitted changes." ;;
            3) sg_info "Switch cancelled."; return 0 ;;
        esac
    fi

    # Show branches and select
    local branches=()
    while IFS= read -r b; do
        [[ "$b" != "$current" ]] && branches+=("$b")
    done < <(git branch --format='%(refname:short)' 2>/dev/null)

    if (( ${#branches[@]} == 0 )); then
        sg_warn "No other branches to switch to."
        return 0
    fi

    local target="${1:-}"
    if [[ -z "$target" ]]; then
        sg_info "Available branches:"
        local i
        for i in "${!branches[@]}"; do
            local age
            age="$(_branch_age "${branches[$i]}")"
            printf "  ${_C_BOLD}%d)${_C_RESET} %-35s ${_C_DIM}%s${_C_RESET}\n" "$((i + 1))" "${branches[$i]}" "$age"
        done
        echo

        local choice
        while true; do
            printf "Select [1-%d] or type branch name: " "${#branches[@]}"
            read -r choice
            if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#branches[@]} )); then
                target="${branches[$((choice - 1))]}"
                break
            elif sg_branch_exists "$choice"; then
                target="$choice"
                break
            fi
            sg_warn "Invalid selection."
        done
    fi

    _sg_cmd checkout "$target" 2>/dev/null || git checkout "$target"
    sg_success "Switched to branch: $target"
}

op_delete() {
    sg_header "Delete Branch"

    local current
    current="$(sg_current_branch)"

    local target
    target="$(sg_prompt "Branch to delete")"

    if [[ -z "$target" ]]; then
        sg_error "Branch name required."
        return 1
    fi

    if [[ "$target" == "$current" ]]; then
        sg_error "Cannot delete the current branch. Switch first."
        return 1
    fi

    if sg_is_protected "$target"; then
        sg_error "Branch '$target' is protected (SG_PROTECTED_BRANCHES). Refusing to delete."
        return 1
    fi

    if ! sg_branch_exists "$target"; then
        sg_error "Branch '$target' does not exist."
        return 1
    fi

    # Check merge status
    local default_branch
    default_branch="$(sg_default_branch)"
    local unmerged
    unmerged="$(git log --oneline "${default_branch}..${target}" 2>/dev/null | wc -l | tr -d ' ')"

    if (( unmerged > 0 )); then
        sg_warn "Branch '$target' has $unmerged unmerged commit(s)."
        sg_warn "These commits exist only on this branch and will be LOST."
        echo
        git log --oneline "${default_branch}..${target}" 2>/dev/null | head -10
        if (( unmerged > 10 )); then
            sg_info "  ... and $((unmerged - 10)) more"
        fi
        echo

        if ! sg_confirm "Force delete unmerged branch '$target'?" "n"; then
            sg_info "Delete cancelled."
            return 0
        fi
        git branch -D "$target"
    else
        if ! sg_confirm "Delete merged branch '$target'?"; then
            sg_info "Delete cancelled."
            return 0
        fi
        git branch -d "$target"
    fi

    sg_success "Deleted branch: $target"

    # Clean up remote tracking
    if _has_tracking "$target" || git show-ref --verify --quiet "refs/remotes/${_REMOTE}/${target}" 2>/dev/null; then
        if sg_confirm "Also delete remote branch '${_REMOTE}/${target}'?" "n"; then
            git push "$_REMOTE" --delete "$target" 2>/dev/null && \
                sg_success "Deleted remote branch: ${_REMOTE}/${target}" || \
                sg_warn "Could not delete remote branch."
        fi
    fi

    sg_undo_available
}

op_rename() {
    sg_header "Rename Branch"

    local current
    current="$(sg_current_branch)"

    local old_name
    old_name="$(sg_prompt "Branch to rename" "$current")"

    if ! sg_branch_exists "$old_name"; then
        sg_error "Branch '$old_name' does not exist."
        return 1
    fi

    if sg_is_protected "$old_name"; then
        sg_error "Branch '$old_name' is protected. Cannot rename."
        return 1
    fi

    local new_name
    new_name="$(sg_prompt "New name")"

    if [[ -z "$new_name" ]]; then
        sg_error "New name cannot be empty."
        return 1
    fi

    new_name="$(_format_branch_name "$new_name")"

    if sg_branch_exists "$new_name"; then
        sg_error "Branch '$new_name' already exists."
        return 1
    fi

    git branch -m "$old_name" "$new_name"
    sg_success "Renamed: $old_name -> $new_name"

    # Handle remote rename
    if git show-ref --verify --quiet "refs/remotes/${_REMOTE}/${old_name}" 2>/dev/null; then
        if sg_confirm "Update remote (delete old, push new)?"; then
            git push "$_REMOTE" --delete "$old_name" 2>/dev/null || true
            git push -u "$_REMOTE" "$new_name"
            sg_success "Remote updated: ${_REMOTE}/$new_name"
        fi
    fi

    sg_undo_available
}

op_track() {
    sg_header "Track / Untrack Remote Branches"

    local action
    action="$(sg_menu "Remote tracking" \
        "Track a remote branch" \
        "Untrack a remote branch" \
        "Show tracking status for all branches"
    )"

    case "$action" in
        1)
            # Fetch latest
            sg_info "Fetching remote branches..."
            git fetch "$_REMOTE" --prune 2>/dev/null || true

            # Show untracked remote branches
            sg_info "Available remote branches (not locally tracked):"
            local remote_branches=()
            while IFS= read -r rb; do
                rb="${rb#${_REMOTE}/}"
                [[ "$rb" == "HEAD" ]] && continue
                if ! sg_branch_exists "$rb"; then
                    remote_branches+=("$rb")
                fi
            done < <(git branch -r --format='%(refname:short)' 2>/dev/null | grep "^${_REMOTE}/" | sed "s|^${_REMOTE}/||")

            if (( ${#remote_branches[@]} == 0 )); then
                sg_info "All remote branches are already tracked locally."
                return 0
            fi

            local i
            for i in "${!remote_branches[@]}"; do
                printf "  ${_C_BOLD}%d)${_C_RESET} %s\n" "$((i + 1))" "${remote_branches[$i]}"
            done
            echo

            local choice
            printf "Select [1-%d]: " "${#remote_branches[@]}"
            read -r choice

            if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#remote_branches[@]} )); then
                local selected="${remote_branches[$((choice - 1))]}"
                git checkout -b "$selected" "${_REMOTE}/${selected}" 2>/dev/null \
                    || git branch --track "$selected" "${_REMOTE}/${selected}"
                sg_success "Now tracking: ${_REMOTE}/$selected"
            else
                sg_warn "Invalid selection."
            fi
            ;;
        2)
            local branch
            branch="$(sg_prompt "Branch to untrack")"
            if [[ -z "$branch" ]]; then
                sg_error "Branch name required."
                return 1
            fi
            git branch --unset-upstream "$branch" 2>/dev/null \
                && sg_success "Untracked: $branch" \
                || sg_error "Could not untrack '$branch'."
            ;;
        3)
            sg_info "Tracking status:"
            echo
            while IFS= read -r branch; do
                local tracking
                tracking="$(_tracking_info "$branch")"
                printf "  %-35s %s\n" "$branch" "$tracking"
            done < <(git branch --format='%(refname:short)' 2>/dev/null)
            ;;
    esac
}

op_stale() {
    sg_header "Stale Branches (older than ${_STALE_DAYS} days)"

    local found=0
    local default_branch
    default_branch="$(sg_default_branch)"

    printf "  ${_C_BOLD}%-35s %-16s %-10s %s${_C_RESET}\n" "BRANCH" "LAST COMMIT" "AGE (DAYS)" "MERGED?"
    sg_divider

    while IFS= read -r branch; do
        local age_days
        age_days="$(_branch_age_days "$branch")"

        if (( age_days >= _STALE_DAYS )); then
            local age_human merged_status
            age_human="$(_branch_age "$branch")"

            # Check if merged into default branch
            if git branch --merged "$default_branch" 2>/dev/null | grep -qw "$branch"; then
                merged_status="${_C_GREEN}yes${_C_RESET}"
            else
                merged_status="${_C_RED}no${_C_RESET}"
            fi

            local color=""
            sg_is_protected "$branch" && color="$_C_YELLOW"

            printf "  ${color}%-35s${_C_RESET} ${_C_DIM}%-16s${_C_RESET} %-10s %b\n" \
                "$branch" "$age_human" "$age_days" "$merged_status"
            (( found++ )) || true
        fi
    done < <(git branch --format='%(refname:short)' 2>/dev/null)

    echo
    if (( found == 0 )); then
        sg_success "No stale branches found."
    else
        sg_info "Found $found stale branch(es)."
        sg_info "Yellow = protected (will not be offered for deletion)."
        echo

        if sg_confirm "Delete all merged, non-protected stale branches?" "n"; then
            while IFS= read -r branch; do
                local age_days
                age_days="$(_branch_age_days "$branch")"
                if (( age_days >= _STALE_DAYS )) && ! sg_is_protected "$branch"; then
                    if git branch --merged "$default_branch" 2>/dev/null | grep -qw "$branch"; then
                        git branch -d "$branch" 2>/dev/null \
                            && sg_success "  Deleted: $branch" \
                            || sg_warn "  Could not delete: $branch"
                    fi
                fi
            done < <(git branch --format='%(refname:short)' 2>/dev/null)
        fi
    fi
}

op_suggest() {
    sg_header "Auto-suggest Branch Name"

    local input
    input="$(sg_prompt "Ticket ID or description (e.g. PROJ-123 or 'fix login redirect')")"

    if [[ -z "$input" ]]; then
        sg_error "Input cannot be empty."
        return 1
    fi

    echo
    sg_info "Suggested branch names:"
    echo

    local slug
    slug="$(_slugify "$input")"

    # Check if it looks like a ticket ID
    local ticket_pattern="${SG_TICKET_PATTERN:-[A-Z]+-[0-9]+}"
    local ticket=""
    ticket="$(echo "$input" | grep -oP "$ticket_pattern" | head -1 || true)"

    local prefixes
    IFS=' ' read -ra prefixes <<< "$(sg_branch_prefixes)"

    local suggestions=()
    for prefix in "${prefixes[@]}"; do
        if [[ -n "$ticket" ]]; then
            suggestions+=("${prefix}/${ticket,,}-${slug}")
            suggestions+=("${prefix}/${ticket,,}")
        else
            suggestions+=("${prefix}/${slug}")
        fi
    done

    local i
    for i in "${!suggestions[@]}"; do
        local s="${suggestions[$i]}"
        local exists_mark=""
        sg_branch_exists "$s" && exists_mark=" ${_C_RED}(exists)${_C_RESET}"
        printf "  ${_C_BOLD}%d)${_C_RESET} %s%b\n" "$((i + 1))" "$s" "$exists_mark"
    done
    echo

    local choice
    printf "Select to create [1-%d] or press Enter to skip: " "${#suggestions[@]}"
    read -r choice

    if [[ -n "$choice" ]] && [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#suggestions[@]} )); then
        local selected="${suggestions[$((choice - 1))]}"
        if sg_branch_exists "$selected"; then
            sg_error "Branch '$selected' already exists."
            return 1
        fi

        local default_branch
        default_branch="$(sg_default_branch)"
        sg_snapshot_create "pre-branch-create: $selected"
        _sg_cmd checkout -b "$selected" "$default_branch" 2>/dev/null \
            || git checkout -b "$selected" "$default_branch"
        sg_success "Created and switched to: $selected"
        sg_undo_available
    fi
}

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

case "${1:-}" in
    create)
        shift
        if [[ -n "${1:-}" ]]; then
            # Direct creation from command line
            sg_snapshot_create "pre-branch-create: $1"
            _sg_cmd checkout -b "$1" "$(sg_default_branch)" 2>/dev/null \
                || git checkout -b "$1" "$(sg_default_branch)"
            sg_success "Created and switched to: $1"
        else
            op_create
        fi
        exit 0 ;;
    list)    op_list; exit 0 ;;
    switch)  shift; op_switch "${1:-}"; exit 0 ;;
    stale)   op_stale; exit 0 ;;
    "")      ;; # fall through to interactive menu
    *)       sg_die "Unknown subcommand: $1" ;;
esac

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

while true; do
    choice="$(sg_menu "SecureGit Branch Manager" \
        "Create branch" \
        "List branches" \
        "Switch branch" \
        "Delete branch" \
        "Rename branch" \
        "Track / untrack remote" \
        "Show stale branches" \
        "Auto-suggest branch name" \
        "Quit"
    )"

    case "$choice" in
        1) op_create ;;
        2) op_list ;;
        3) op_switch ;;
        4) op_delete ;;
        5) op_rename ;;
        6) op_track ;;
        7) op_stale ;;
        8) op_suggest ;;
        9) sg_info "Goodbye."; exit 0 ;;
    esac

    echo
    sg_divider
done
