#!/usr/bin/env bash
set -u

PROGRAM="$(basename "$0")"
REMOTE="${SYNC_REMOTE:-origin}"

usage() {
  cat <<'EOF'
Usage:
  bash scripts/sync_workflow.sh pull
  bash scripts/sync_workflow.sh upload "commit message" [-- path ...]
  bash scripts/sync_workflow.sh run "commit message" -- <command> [args...]
  bash scripts/sync_workflow.sh status

Workflow:
  pull    Fetch and pull latest cloud changes before work.
  upload  Commit local repository changes and push them to the cloud.
          Add "-- path ..." to upload only specific files or directories.
  run     Pull, execute a command, then upload changes.
  status  Show current Git sync status.

Environment:
  SYNC_REMOTE=origin   Override the Git remote name.
EOF
}

info() {
  printf '[%s] %s\n' "$PROGRAM" "$*"
}

die() {
  printf '[%s] ERROR: %s\n' "$PROGRAM" "$*" >&2
  exit 1
}

git_auth_hint() {
  cat >&2 <<'EOF'

Codeup authentication hint:
  Git is available, but Codeup credentials may be missing or unavailable.
  For HTTPS, Codeup needs the HTTPS clone account plus HTTPS clone password/token.
  If you see "terminal prompts disabled", configure credentials first.
  See docs/codeup-auth.md.
EOF
}

run_git_remote() {
  local label="$1"
  shift

  local output
  local status
  output="$("$@" 2>&1)"
  status=$?
  if [ "$status" -ne 0 ]; then
    printf '%s\n' "$output" >&2
    case "$output" in
      *"could not read Username"*|*"terminal prompts disabled"*|*"Authentication failed"*|*"access denied"*|*"403"*)
        git_auth_hint
        die "$label failed because Codeup authentication is missing or invalid."
        ;;
    esac
    die "$label failed."
  fi

  if [ -n "$output" ]; then
    printf '%s\n' "$output"
  fi
}

repo_root() {
  git rev-parse --show-toplevel 2>/dev/null
}

require_repo() {
  local root
  root="$(repo_root)" || die "Run this command inside the project Git repository."
  cd "$root" || die "Cannot enter repository root: $root"
}

require_remote() {
  git remote get-url "$REMOTE" >/dev/null 2>&1 || die "Git remote '$REMOTE' is not configured."
}

current_branch() {
  local branch
  branch="$(git branch --show-current)"
  [ -n "$branch" ] || die "Detached HEAD is not supported by this simple workflow."
  printf '%s\n' "$branch"
}

ensure_upstream() {
  local branch="$1"
  if git rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1; then
    return 0
  fi

  if git show-ref --verify --quiet "refs/remotes/$REMOTE/$branch"; then
    info "Setting upstream to $REMOTE/$branch."
    git branch --set-upstream-to="$REMOTE/$branch" "$branch" >/dev/null
    return 0
  fi

  return 1
}

has_worktree_changes() {
  ! git diff --quiet || ! git diff --cached --quiet || [ -n "$(git ls-files --others --exclude-standard)" ]
}

require_commit_identity() {
  git config user.name >/dev/null 2>&1 || die "Git user.name is not set. Run: git config user.name \"Your Name\""
  git config user.email >/dev/null 2>&1 || die "Git user.email is not set. Run: git config user.email \"you@example.com\""
}

sync_pull() {
  require_repo
  require_remote

  local branch
  branch="$(current_branch)"

  info "Fetching $REMOTE."
  run_git_remote "Fetch" git fetch "$REMOTE"

  if ensure_upstream "$branch"; then
    info "Pulling latest changes for $branch with rebase/autostash."
    run_git_remote "Pull" git pull --rebase --autostash "$REMOTE" "$branch"
  else
    info "No cloud branch $REMOTE/$branch yet. Skipping pull; upload will create it when needed."
  fi
}

sync_upload() {
  require_repo
  require_remote

  local message="${1:-}"
  if [ "$#" -gt 0 ]; then
    shift
  fi
  local paths=()
  if [ "${1:-}" = "--" ]; then
    shift
    paths=("$@")
  elif [ "$#" -gt 0 ]; then
    die "Unexpected upload arguments. Use: upload \"message\" -- path ..."
  fi

  local branch
  branch="$(current_branch)"

  if [ -z "$message" ]; then
    message="sync: update $(date '+%Y-%m-%d %H:%M:%S')"
  fi

  if [ "${#paths[@]}" -eq 0 ] && ! has_worktree_changes; then
    info "No local changes to upload."
    return 0
  fi

  require_commit_identity
  if [ "${#paths[@]}" -gt 0 ]; then
    info "Staging selected paths: ${paths[*]}"
    git add -A -- "${paths[@]}" || die "git add failed."
  else
    info "Staging repository changes. Ignored files, such as data/raw and data/processed, stay local."
    git add -A || die "git add failed."
  fi

  if git diff --cached --quiet; then
    info "No committable changes after applying .gitignore."
    return 0
  fi

  info "Committing changes."
  git commit -m "$message" || die "Commit failed."

  if ensure_upstream "$branch"; then
    info "Pushing to $REMOTE/$branch."
    run_git_remote "Push" git push "$REMOTE" "$branch"
  else
    info "Creating cloud branch $REMOTE/$branch."
    run_git_remote "Push" git push -u "$REMOTE" "$branch"
  fi
}

show_status() {
  require_repo
  require_remote

  local branch
  branch="$(current_branch)"

  info "Repository: $(pwd)"
  info "Remote: $REMOTE ($(git remote get-url "$REMOTE"))"
  info "Branch: $branch"
  git status --short --branch
}

run_with_sync() {
  local message="${1:-}"
  shift || true

  [ "${1:-}" = "--" ] || die "Use: bash scripts/sync_workflow.sh run \"message\" -- <command> [args...]"
  shift
  [ "$#" -gt 0 ] || die "Missing command after --."

  sync_pull

  info "Running task command: $*"
  "$@"
  local task_status=$?

  if [ "$task_status" -ne 0 ]; then
    info "Task command exited with status $task_status. Uploading any captured changes, then returning the task status."
  fi

  sync_upload "$message"
  local upload_status=$?

  if [ "$task_status" -ne 0 ]; then
    return "$task_status"
  fi
  return "$upload_status"
}

main() {
  local command="${1:-}"
  shift || true

  case "$command" in
    pull)
      sync_pull
      ;;
    upload)
      sync_upload "$@"
      ;;
    run)
      run_with_sync "$@"
      ;;
    status)
      show_status
      ;;
    -h|--help|help|'')
      usage
      ;;
    *)
      usage >&2
      die "Unknown command: $command"
      ;;
  esac
}

main "$@"
