#!/bin/bash
# rosaclone - clone ROSA Linux project git repositories

# ---------------- config definition (BEFORE sourcing lib) ----------------

config_order="ABF_URL_HTTPS ABF_URL_SSH ABF_OWNER ABF_PLATFORM \
FAB_URL_HTTPS FAB_URL_SSH FAB_OWNER FAB_PLATFORM FAB_BRANCHES \
GIT_PROTO DEFAULT_SOURCE BASE_DIR"

declare -A config_defaults
config_defaults[ABF_URL_HTTPS]=https://abf.rosa.ru/
config_defaults[ABF_URL_SSH]=git@abf.rosa.ru:
config_defaults[ABF_OWNER]=import
config_defaults[ABF_PLATFORM]=rosa2023.1
config_defaults[FAB_URL_HTTPS]=https://gitea.rosa.ru/
config_defaults[FAB_URL_SSH]=ssh://git@gitea.rosa.ru:222/
config_defaults[FAB_OWNER]=rosalinux
config_defaults[FAB_PLATFORM]=rosa14
config_defaults[FAB_BRANCHES]="rosa14"
config_defaults[GIT_PROTO]=https
config_defaults[DEFAULT_SOURCE]=abf
config_defaults[BASE_DIR]=

# ---------------- find and source library ----------------

script_dir=$(cd "$(dirname "$0")" && pwd)
lib_found=0
for libdir in "$script_dir" /usr/share/rosaclone; do
  if [ -f "$libdir/rosaclone-lib.sh" ]; then
    # shellcheck disable=SC1091
    . "$libdir/rosaclone-lib.sh"
    lib_found=1
    break
  fi
done
if [ "$lib_found" -eq 0 ]; then
  echo "error: rosaclone-lib.sh not found" >&2
  exit 1
fi

# ---------------- defaults (env already saved by library) ----------------

ABF_URL_HTTPS=""
ABF_URL_SSH=""
ABF_OWNER=""
ABF_PLATFORM=""

FAB_URL_HTTPS=""
FAB_URL_SSH=""
FAB_OWNER=""
FAB_PLATFORM=""
FAB_BRANCHES=""

GIT_PROTO="https"
DEFAULT_SOURCE="abf"
BASE_DIR=""

# CLI overrides
opt_abf=0
opt_fab=0
opt_a=0
opt_ssh=0
opt_https=0
opt_force=0
opt_auth=""
opt_dest=""
opt_init_config=0
opt_debug=0

# state
p_owner_explicit=0

# ---------------- usage ----------------

usage() {
  cat <<'EOT'
usage:
  rosaclone [OPTIONS] <arg>

rosaclone - clone ROSA Linux project git repositories

  The tool clones a project's git repository from the ROSA Linux
  infrastructure and can combine the old ABF (abf.rosa.ru) and the
  new ABF (gitea.rosa.ru) into a single local git working directory.

  After a single clone you can work with old ABF branches
  (through rosa2023.1) and new ABF branches (from rosa14 onward)
  as if they all lived in one git repository.

Each source is registered as a separate git remote:

  origin  - old ABF (abf.rosa.ru, group 'import' by default)
  fab     - new ABF (gitea.rosa.ru, group 'rosalinux' by default)

  This makes it possible to keep using the familiar workflow with
  old platform branches while also working with new platform branches,
  without switching between two separate clones.

modes:
  --abf  | -a    clone from abf only
  --fab  | -f    clone from fab only
  --both | -b    fork mode: origin = abf, remote 'fab' = fab,
                 branches from FAB_BRANCHES are created from fab/<branch>

  if neither -a, -f, -b is given, DEFAULT_SOURCE from config is used
  (abf | fab | both)

<arg> for --abf / --fab:
  full URL              e.g. https://abf.rosa.ru/import/dracut.git
                        e.g. git@abf.rosa.ru:import/dracut.git
  owner/project         e.g. import/dracut
  project               e.g. dracut

<arg> for --both:
  full URL from abf     e.g. https://abf.rosa.ru/import/dracut.git
  full URL from fab     e.g. https://gitea.rosa.ru/rosalinux/dracut.git
  project               e.g. dracut

  owner/project is not allowed for --both

protocol:
  --ssh                 use ssh (ABF_URL_SSH / FAB_URL_SSH)
  --https, --http       use http(s) (ABF_URL_HTTPS / FAB_URL_HTTPS)

  if neither is given, GIT_PROTO from config is used
  --ssh and --https/--http are mutually exclusive

common:
  -F, --force           remove destination dir before clone
                        (only if it is a git repository)
  -d, --dest=DIR        destination base dir (overrides BASE_DIR)
  --auth=USER[:PASS]    only used for http(s):// URLs
  --nocd                ignored; used by the shell wrapper to skip cd
  --init-config         regenerate user config from defaults
  --debug               print debug info to stderr
  --help

config:
  /etc/rosaclone/config           (system defaults)
  ~/.config/rosaclone/config      (overrides system)

env:
  any config variable can be overridden by environment,
  e.g. BASE_DIR=/tmp rosaclone ...
EOT
  exit 1
}

# ---------------- argument parsing ----------------

parse_args() {
  debug "parse_args: argv=$*"
  while [ $# -gt 0 ]; do
    debug "parse_args: arg='$1'"
    case "$1" in
      '--abf'  | '-a' )        opt_abf=1 ;;
      '--fab'  | '-f' )        opt_fab=1 ;;
      '--both' | '-b' )        opt_a=1 ;;
      '--ssh' )                opt_ssh=1 ;;
      '--https' | '--http' )   opt_https=1 ;;
      '-F' | '--force' )       opt_force=1 ;;
      '-d' )
        shift
        [ $# -gt 0 ] || die "-d requires a value"
        opt_dest="$1"
        ;;
      --dest=*)                opt_dest="${1#--dest=}" ;;
      --dest)
        shift
        [ $# -gt 0 ] || die "--dest requires a value"
        opt_dest="$1"
        ;;
      --auth=*)                opt_auth="${1#--auth=}" ;;
      '--nocd' )               ;;   # internal arg, do nothing
      '--init-config' )        opt_init_config=1 ;;
      '--debug' )              opt_debug=1 ;;
      '--help' | '-h')         usage ;;
      -*)
        die "unknown option: $1"
        ;;
      *)
        if [ -n "${ARG:-}" ]; then
          die "only one project argument is supported"
        fi
        ARG="$1"
        ;;
    esac
    shift
  done
  debug "parse_args: ARG='${ARG:-}' opt_abf=$opt_abf opt_fab=$opt_fab opt_a=$opt_a opt_ssh=$opt_ssh opt_https=$opt_https opt_force=$opt_force opt_dest='$opt_dest'"
}

# ---------------- mode validation ----------------

validate_modes() {
  debug "validate_modes: opt_a=$opt_a opt_abf=$opt_abf opt_fab=$opt_fab opt_ssh=$opt_ssh opt_https=$opt_https GIT_PROTO=$GIT_PROTO DEFAULT_SOURCE=$DEFAULT_SOURCE"

  if [ "$opt_a" -eq 1 ] && { [ "$opt_abf" -eq 1 ] || [ "$opt_fab" -eq 1 ]; }; then
    die "-b cannot be combined with --abf or --fab"
  fi

  if [ "$opt_ssh" -eq 1 ] && [ "$opt_https" -eq 1 ]; then
    die "only one of --ssh, --https is allowed"
  fi

  if [ "$opt_ssh" -eq 1 ]; then
    GIT_PROTO=ssh
  elif [ "$opt_https" -eq 1 ]; then
    GIT_PROTO=https
  fi
  debug "validate_modes: GIT_PROTO=$GIT_PROTO"

  if [ "$opt_a" -eq 0 ] && [ "$opt_abf" -eq 0 ] && [ "$opt_fab" -eq 0 ]; then
    case "$DEFAULT_SOURCE" in
      abf)  opt_abf=1 ;;
      fab)  opt_fab=1 ;;
      both) opt_a=1 ;;
      *)
        die "unknown DEFAULT_SOURCE: '$DEFAULT_SOURCE' (expected: abf | fab | both)"
        ;;
    esac
    debug "validate_modes: using DEFAULT_SOURCE=$DEFAULT_SOURCE"
  fi

  case "$GIT_PROTO" in
    https|ssh) ;;
    *) die "unsupported GIT_PROTO: '$GIT_PROTO' (expected: https | ssh)" ;;
  esac

  if [ "$opt_a" -eq 1 ]; then
    [ -n "$ABF_OWNER" ] || die "ABF_OWNER is not set in config"
    [ -n "$FAB_OWNER" ] || die "FAB_OWNER is not set in config"
  elif [ "$opt_fab" -eq 1 ]; then
    [ -n "$FAB_OWNER" ] || die "FAB_OWNER is not set in config"
  else
    [ -n "$ABF_OWNER" ] || die "ABF_OWNER is not set in config"
  fi
}

# ---------------- project arg parsing ----------------

parse_arg_single() {
  local arg="$1" default_owner="$2"
  debug "parse_arg_single: arg='$arg' default_owner='$default_owner'"
  case "$arg" in
    *://*|*@*:*)
      p_owner=$(extract_owner_from_url "$arg")
      p_project=$(extract_project_from_url "$arg")
      p_owner_explicit=1
      ;;
    */*)
      p_owner="${arg%%/*}"
      p_project="${arg#*/}"
      p_owner_explicit=1
      ;;
    *)
      p_owner="$default_owner"
      p_project="$arg"
      p_owner_explicit=0
      ;;
  esac
  debug "parse_arg_single: p_owner='$p_owner' p_project='$p_project' p_owner_explicit=$p_owner_explicit"
  [ -n "$p_owner" ] || die "cannot determine owner from '$arg'"
  [ -n "$p_project" ] || die "cannot determine project from '$arg'"
}

parse_arg_both() {
  local arg="$1"
  debug "parse_arg_both: arg='$arg'"
  case "$arg" in
    *://*|*@*:*)
      local host owner url_pair
      host=$(extract_host "$arg")
      owner=$(extract_owner_from_url "$arg")
      p_project=$(extract_project_from_url "$arg")
      p_owner_explicit=1
      debug "parse_arg_both: host='$host' owner='$owner' project='$p_project'"
      [ -n "$p_project" ] || die "cannot determine project from '$arg'"

      url_pair="$host/$owner"

      local abf_base fab_base abf_host fab_host abf_pair fab_pair
      case "$GIT_PROTO" in
        https) abf_base="$ABF_URL_HTTPS"; fab_base="$FAB_URL_HTTPS" ;;
        ssh)   abf_base="$ABF_URL_SSH";   fab_base="$FAB_URL_SSH" ;;
      esac

      abf_host=$(extract_host "$abf_base")
      fab_host=$(extract_host "$fab_base")
      abf_pair="$abf_host/$ABF_OWNER"
      fab_pair="$fab_host/$FAB_OWNER"

      debug "parse_arg_both: abf_pair='$abf_pair' fab_pair='$fab_pair'"

      if [ "$url_pair" = "$abf_pair" ] || [ "$url_pair" = "$fab_pair" ]; then
        debug "parse_arg_both: host/owner matches: $url_pair"
      else
        die "URL '$arg' does not match '$abf_pair' or '$fab_pair'"
      fi
      ;;
    */*)
      die "-b does not accept owner/project: '$arg'"
      ;;
    *)
      p_project="$arg"
      p_owner_explicit=0
      ;;
  esac
  [ -n "$p_project" ] || die "cannot determine project from '$arg'"
  debug "parse_arg_both: p_project='$p_project' p_owner_explicit=$p_owner_explicit"
}

# ---------------- git operations ----------------

compute_target_dir() {
  local owner="$1" project="$2"
  local base

  if [ -n "$opt_dest" ]; then
    base="$opt_dest"
    debug "compute_target_dir: using -d/--dest: '$base'"
  elif [ -n "$BASE_DIR" ]; then
    base="$BASE_DIR"
    debug "compute_target_dir: using BASE_DIR: '$base'"
  else
    base=""
  fi

  if [ -n "$base" ]; then
    target_dir="$base/$owner/$project"
    mkdir -p "$base/$owner" || die "cannot create $base/$owner"
  else
    target_dir="$project"
  fi

  debug "compute_target_dir: target_dir='$target_dir'"

  if [ "$opt_force" -eq 1 ]; then
    safe_rm_for_clone "$target_dir"
  fi
}

print_project_dir() {
  pwd
}

do_mode_both() {
  local project="$1"

  local abf_url fab_url
  abf_url=$(build_url abf "$ABF_OWNER" "$project")
  abf_url=$(url_with_auth "$abf_url" "$opt_auth")
  fab_url=$(build_url fab "$FAB_OWNER" "$project")

  debug "do_mode_both: abf_url='$abf_url' fab_url='$fab_url'"

  log "cloning $abf_url -> $target_dir"
  if ! git clone "$abf_url" "$target_dir"; then
    die "cannot clone abf repository '$abf_url' (mode -b requires both abf and fab)"
  fi
  cd "$target_dir" || die "cannot cd into $target_dir"

  if [ "$p_owner_explicit" -eq 0 ]; then
    ensure_target_branches origin "$ABF_PLATFORM"
  fi

  if ! setup_fab_remote "$fab_url"; then
    local target_dir_abs
    target_dir_abs=$(pwd)

    warn "cannot set up fab remote '$fab_url' (mode -b requires both abf and fab); abf cloned to '$target_dir_abs'"

    if ask_yes_no "remove '$target_dir_abs'? (yes/no):" yes; then
      cd "${OLDPWD:-..}" || die "cannot cd out of '$target_dir_abs'"
      safe_rm_for_clone "$target_dir_abs"
      log "removed '$target_dir_abs'"
    fi
    exit 1
  fi

  if [ "$p_owner_explicit" -eq 0 ]; then
    ensure_fab_branches "$ABF_PLATFORM"
  fi

  do_final_checkout "$FAB_PLATFORM"
  show_branches_info $ABF_PLATFORM $FAB_PLATFORM $FAB_BRANCHES
  print_project_dir
}

do_clone_one() {
  local project="$1" owner="$2"
  local url platform host

  if [ "$opt_fab" -eq 1 ]; then
    host=fab
    platform="$FAB_PLATFORM"
  else
    host=abf
    platform="$ABF_PLATFORM"
  fi

  url=$(build_url "$host" "$owner" "$project")
  url=$(url_with_auth "$url" "$opt_auth")

  debug "do_clone_one: url='$url' platform='$platform' p_owner_explicit=$p_owner_explicit"

  log "cloning $url -> $target_dir"
  git clone "$url" "$target_dir" || die "git clone failed"
  cd "$target_dir" || die "cannot cd into $target_dir"

  if [ "$p_owner_explicit" -eq 0 ]; then
    if [ "$opt_fab" -eq 1 ]; then
      ensure_target_branches origin "$FAB_PLATFORM" $FAB_BRANCHES
    else
      ensure_target_branches origin "$ABF_PLATFORM"
    fi
  fi

  do_final_checkout "$platform"
  print_project_dir
}

# ---------------- main ----------------

main() {
  case "${1:-}" in
    -h|--help|help)
      usage
      ;;
  esac

  check_git

  for a in "$@"; do
    case "$a" in
      --debug)       opt_debug=1 ;;
      --init-config) opt_init_config=1 ;;
    esac
  done

  debug "main: argv=$*"
  debug "main: opt_debug=$opt_debug"

  load_config
  parse_args "$@"
  print_config_vars

  if [ "$opt_init_config" -eq 1 ] && [ -z "${ARG:-}" ]; then
    return 0
  fi

  if [ -z "${ARG:-}" ]; then
    usage
  fi

  validate_modes

  if [ "$opt_a" -eq 1 ]; then
    parse_arg_both "$ARG"
    compute_target_dir "$ABF_OWNER" "$p_project"
    do_mode_both "$p_project"
  else
    if [ "$opt_fab" -eq 1 ]; then
      parse_arg_single "$ARG" "$FAB_OWNER"
    else
      parse_arg_single "$ARG" "$ABF_OWNER"
    fi
    compute_target_dir "$p_owner" "$p_project"
    do_clone_one "$p_project" "$p_owner"
  fi
}

ARG=""
p_owner=""
p_project=""
target_dir=""

main "$@"
