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

# git-branch-cleanup.sh
#
# Safe branch cleanup workflow:
# - Prunes stale remote-tracking references
# - Lists local branches merged into a target branch
# - Protects common mainline branches and the current branch
# - Defaults to dry-run mode
# - Requires explicit confirmation before deletion
#
# Usage examples:
#   ./git-branch-cleanup.sh
#   ./git-branch-cleanup.sh --remote origin --target origin/main --apply
#   ./git-branch-cleanup.sh --apply --force --verbose

REMOTE="origin"
TARGET_BRANCH=""
DRY_RUN=1
FORCE=0
VERBOSE=0
PROTECTED_BRANCHES=("main" "master" "develop" "release" "staging")

usage() {
  cat <<'EOF'
Usage:
  git-branch-cleanup.sh [--remote REMOTE] [--target BRANCH] [--apply] [--force] [--verbose]

Options:
  --remote REMOTE   Remote to prune and inspect (default: origin)
  --target BRANCH   Target branch used to determine merged branches
  --apply           Actually delete branches (default: dry-run only)
  --force           Use -D instead of -d for branch deletion (use with care)
  --verbose         Print extra diagnostic output
  -h, --help        Show this help

Examples:
  ./git-branch-cleanup.sh
  ./git-branch-cleanup.sh --remote origin --target origin/main --apply
  ./git-branch-cleanup.sh --apply --force
EOF
}

log() {
  printf '%s\n' "$*"
}

err() {
  printf 'ERROR: %s\n' "$*" >&2
}

is_git_repo() {
  git rev-parse --is-inside-work-tree >/dev/null 2>&1
}

current_branch() {
  git symbolic-ref --quiet --short HEAD 2>/dev/null || true
}

resolve_target_branch() {
  if [[ -n "$TARGET_BRANCH" ]]; then
    return 0
  fi

  if git show-ref --verify --quiet "refs/remotes/$REMOTE/main"; then
    TARGET_BRANCH="$REMOTE/main"
  elif git show-ref --verify --quiet "refs/remotes/$REMOTE/master"; then
    TARGET_BRANCH="$REMOTE/master"
  elif git show-ref --verify --quiet "refs/heads/main"; then
    TARGET_BRANCH="main"
  elif git show-ref --verify --quiet "refs/heads/master"; then
    TARGET_BRANCH="master"
  else
    err "Unable to infer a target branch. Set --target explicitly."
    exit 1
  fi
}

is_protected() {
  local branch="$1"
  local current="$2"

  if [[ "$branch" == "$current" ]]; then
    return 0
  fi

  for protected in "${PROTECTED_BRANCHES[@]}"; do
    if [[ "$branch" == "$protected" || "$branch" == "$REMOTE/$protected" ]]; then
      return 0
    fi
  done

  return 1
}

main() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --remote)
        REMOTE="${2:-}"
        shift 2
        ;;
      --target)
        TARGET_BRANCH="${2:-}"
        shift 2
        ;;
      --apply)
        DRY_RUN=0
        shift
        ;;
      --force)
        FORCE=1
        shift
        ;;
      --verbose)
        VERBOSE=1
        shift
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      *)
        err "Unknown argument: $1"
        usage
        exit 1
        ;;
    esac
  done

  if ! command -v git >/dev/null 2>&1; then
    err "Git is not installed or not available on PATH."
    exit 1
  fi

  if ! is_git_repo; then
    err "Current directory is not inside a Git repository."
    exit 1
  fi

  if [[ -z "$REMOTE" ]]; then
    err "Remote name cannot be empty."
    exit 1
  fi

  local current
  current="$(current_branch)"

  resolve_target_branch

  log "Remote: $REMOTE"
  log "Target: $TARGET_BRANCH"
  log "Mode: $([[ "$DRY_RUN" -eq 1 ]] && echo dry-run || echo apply)"
  log "Deletion: $([[ "$FORCE" -eq 1 ]] && echo force || echo safe)"

  if [[ "$VERBOSE" -eq 1 ]]; then
    log "Current branch: ${current:-detached HEAD}"
  fi

  log "Pruning remote-tracking refs..."
  git fetch --prune "$REMOTE"

  log "Collecting merged local branches..."
  mapfile -t merged_branches < <(
    git branch --merged "$TARGET_BRANCH" --format='%(refname:short)' \
      | sed '/^\*/d' \
      | awk 'NF'
  )

  if [[ ${#merged_branches[@]} -eq 0 ]]; then
    log "No merged local branches found for cleanup."
    exit 0
  fi

  deletable=()
  skipped=()

  for branch in "${merged_branches[@]}"; do
    if is_protected "$branch" "$current"; then
      skipped+=("$branch")
      continue
    fi
    deletable+=("$branch")
  done

  if [[ ${#skipped[@]} -gt 0 && "$VERBOSE" -eq 1 ]]; then
    log "Skipped protected branches: ${skipped[*]}"
  fi

  if [[ ${#deletable[@]} -eq 0 ]]; then
    log "No eligible branches to delete after protection checks."
    exit 0
  fi

  log "Eligible branches:"
  printf '  %s\n' "${deletable[@]}"

  if [[ "$DRY_RUN" -eq 1 ]]; then
    log "Dry-run enabled. No branches were deleted. Re-run with --apply to delete local branches."
    exit 0
  fi

  printf 'Proceed with deleting %d branches? [y/N] ' "${#deletable[@]}"
  read -r reply
  if [[ ! "$reply" =~ ^[Yy]$ ]]; then
    log "Deletion cancelled by user."
    exit 1
  fi

  delete_flag='-d'
  if [[ "$FORCE" -eq 1 ]]; then
    delete_flag='-D'
  fi

  deleted=()
  failed=()

  for branch in "${deletable[@]}"; do
    if git branch "$delete_flag" "$branch"; then
      deleted+=("$branch")
    else
      failed+=("$branch")
      err "Failed to delete branch: $branch"
    fi
  done

  log "Cleanup summary:"
  log "  Deleted: ${#deleted[@]}"
  log "  Failed: ${#failed[@]}"

  if [[ ${#failed[@]} -gt 0 ]]; then
    exit 2
  fi
}

main "$@"