#!/usr/bin/env bash

set -e

source "$HOME/.bin/barg"

# shellcheck disable=SC2034
SPEC=(
  "command;flow;DevFlow CLI - Manage instances and development containers"
  "note;Use 'flow <command> --help' for command-specific options"

  "command;enter;Connect to a development instance via SSH"
  "note;Target format: [user@]namespace@platform (e.g., 'personal@orb' or 'root@personal@orb')"
  "argument;user,u;type:option;help:SSH user (overrides user in target)"
  "argument;namespace,n;type:option;help:Instance namespace (overrides namespace in target)"
  "argument;platform,p;type:option;help:Platform name (overrides platform in target)"
  "argument;session,s;type:option;default:default;help:Development session name (default: 'default')"
  "argument;no-tmux;type:flag;dest:no_tmux;default:false;help:Skip tmux attachment on connection"
  "argument;dry-run,d;type:flag;dest:dry_run;default:false;help:Show SSH command without executing"
  "argument;target,t;required;help:Target instance in format [user@]namespace@platform"
  "argument;ssh-args;type:rest;dest:ssh_args;help:Additional SSH arguments (after --)"
  "end"

  "command;sync;Git tools"
  "command;check;Check all projects status"
  "end"
  "end"

  "command;dotfiles;Manage repository dotfiles and git submodules"
  "command;init;Initialize and set up all submodules"
  "note;Equivalent to: git submodule update --init --recursive"
  "end"
  "command;pull;Update all submodules to latest remote commits"
  "note;Equivalent to: git submodule update --remote --recursive"
  "end"
  "command;urls;Synchronize submodule URLs"
  "note;Equivalent to: git submodule sync --recursive"
  "end"
  "command;reset;Reset submodules to the recorded commits"
  "note;Equivalent to: git submodule update --init --recursive --force"
  "end"
  "command;status;Show current submodule status"
  "note;Equivalent to: git submodule status --recursive"
  "end"
  "command;all;Run URLs sync, initialization, and remote update in one step"
  "note;Equivalent to: git submodule sync --recursive && git submodule update --init --recursive && git submodule update --remote --recursive"
  "end"
  "end"

  "command;create;Create and start a new development container"
  "argument;image,i;required;type:option;help:Container image to use (with optional tag)"
  "argument;project,p;type:option;help:Path to local project directory"
  "argument;name;required;help:Container name"
  "end"

  "command;exec;Execute a command or open a shell in a container"
  "argument;name;required;help:Container name"
  "argument;cmd;type:rest;help:Command to execute inside container (after --)"
  "end"

  "command;connect;Attach or switch to the container’s tmux session"
  "note;When already inside tmux, switches to the target session instead of reattaching."
  "note;New tmux panes or windows in the session automatically start inside the container."
  "argument;from,f;type:option;dest:name;help:Optional source container name"
  "argument;name;required;help:Target container name"
  "end"

  "command;list;Display all development containers and their status"
  "end"

  "command;stop;Stop or kill a running development container"
  "argument;from;type:option;dest:name;help:Optional source container name"
  "argument;kill;type:flag;help:Use kill instead of graceful stop"
  "argument;name;required;help:Target container name"
  "end"

  "command;remove,rm;Remove a development container"
  "argument;from;type:option;dest:name;help:Optional source container name"
  "argument;force,f;type:flag;help:Force removal of container"
  "argument;name;required;help:Target container name"
  "end"

  "command;respawn;Restart all tmux panes for a development session"
  "argument;from;type:option;dest:name;help:Optional source container name"
  "argument;name;required;help:Session or container name"
  "end"

  "command;test;Verify that the dev script is functioning"
  "argument;from;type:option;dest:name;help:Optional source container name"
  "argument;name;help:Target container name"
  "end"

  "end"
)

DEFAULT_REGISTRY="registry.tomastm.com"
DEFAULT_TAG="latest"
PROJECT_DIR="$HOME/projects"
PROJECT_ABBR="p"
CONTAINER_HOME="/home/dev"

fail() {
  printf 'Error: %b\n' "$*" >&2
  exit 1
}

resolve_path() {
  local path="${1:-$(dirname "${BASH_SOURCE[0]}")}"
  if command -v realpath >/dev/null 2>&1; then
    realpath "$path"
  else
    echo "$(cd "$(dirname "$path")" && pwd)/$(basename "$path")"
  fi
}

# shellcheck disable=SC2178,SC2128
parse_image_ref() {
  local input="$1"

  local image_ref registry repo tag label

  if [[ $input == */* ]]; then
    local prefix="${input%%/*}"
    if [[ "$prefix" == "docker" ]]; then
      input="docker.io/library/${input#*/}"
    elif [[ "$prefix" == "tm0" ]]; then
      input="${DEFAULT_REGISTRY}/${input#*/}"
    fi

    registry="${input%%/*}"
    input=${input#*/}
  else
    registry="$DEFAULT_REGISTRY"
  fi

  if [[ "${input##*/}" == *:* ]]; then
    tag="${input##*:}"
    input="${input%:*}"
  else
    tag="$DEFAULT_TAG"
  fi

  repo="${registry}/${input}"
  repo="${repo#*/}"
  image_ref="${registry}/${repo}:${tag}"

  label="${registry%.*}"
  label="${label##*.}/${repo##*/}"

  echo "$image_ref $repo $tag $label"
}

# shellcheck disable=SC2154,SC2155
docker_container_exists() {
  local cname="$(get_cname)"
  docker container ls -a --format '{{.Names}}' | grep -Fqx "$cname"
}

# shellcheck disable=SC2154,SC2155
docker_container_running() {
  local cname="$(get_cname)"
  docker container ls --format '{{.Names}}' | grep -Fqx "$cname"
}

docker_image_present() {
  local ref="$1"
  docker image inspect "$ref" >/dev/null 2>&1
}

# shellcheck disable=SC2154,SC2155
get_cname() {
  printf "%s" "dev-${name_arg#dev-}"
}

cmd() {
  barg_usage
}

cmd_dotfiles_init() {
  echo "[dotfiles] Initializing submodules..."
  git submodule update --init --recursive
}

cmd_dotfiles_pull() {
  echo "[dotfiles] Updating submodules to latest remote commits..."
  git submodule update --remote --recursive
}

cmd_dotfiles_urls() {
  echo "[dotfiles] Syncing submodule URLs..."
  git submodule sync --recursive
}

cmd_dotfiles_reset() {
  echo "[dotfiles] Resetting submodules to recorded commits..."
  git submodule update --init --recursive --force
}

cmd_dotfiles_status() {
  echo "[dotfiles] Submodule status:"
  git submodule status --recursive
}

cmd_dotfiles_all() {
  echo "[dotfiles] Syncing URLs..."
  git submodule sync --recursive

  echo "[dotfiles] Initializing submodules..."
  git submodule update --init --recursive

  echo "[dotfiles] Updating submodules to latest remote commits..."
  git submodule update --remote --recursive
}

# shellcheck disable=SC2154,SC2155
cmd_enter() {
  # VARS: user_arg, namespace_arg, platform_arg, target_arg, session_arg, no_tmux_arg, dry_run_arg, ssh_args_arg

  # Do not run inside instance
  if [[ -n "$DF_NAMESPACE" && -n "$DF_PLATFORM" ]]; then
    fail "It is not recommended to run this command inside an instance.\nCurrently inside: $(tput bold)${DF_NAMESPACE}@${DF_PLATFORM}$(tput sgr0)"
  fi

  local -A CONFIG_HOST=(
    [orb.host]="<namespace>@orb"
    [utm.host]="<namespace>.utm.local"
    [core.host]="<namespace>.core.lan"
  )

  local df_platform=""
  local df_namespace=""
  local df_user=""

  # Parse target: get user, namespace, platform
  if [[ "$target_arg" == *@* ]]; then
    df_platform="${target_arg##*@}"
    target_arg="${target_arg%@*}"
  fi

  if [[ "$target_arg" == *@* ]]; then
    df_namespace="${target_arg##*@}"
    df_user="${target_arg%@*}"
  else
    df_namespace="${target_arg}"
    df_user="${USER}"
  fi

  if [[ -n "$platform_arg" ]]; then
    df_platform="$platform_arg"
  fi
  if [[ -n "$namespace_arg" ]]; then
    df_namespace="$namespace_arg"
  fi
  if [[ -n "$user_arg" ]]; then
    df_user="$user_arg"
  fi

  # Resolve host, identity (maybe check what would the host be in order to use .ssh/config)
  local host_config="${CONFIG_HOST[${df_platform}.host]}"
  local ssh_host="${host_config//<namespace>/$df_namespace}"
  if [[ -z "$ssh_host" ]]; then
    fail "Invalid platform: ${df_platform}"
  fi

  # Build ssh cmd: ssh + identity + tmux + envs
  local ssh_cmd=(ssh -tt "${df_user}@${ssh_host}")

  if [[ "$no_tmux_arg" == "false" ]]; then
    # TODO: instead of tmux,maybe use "flow" in order to attach to dev container too
    ssh_cmd+=("tmux" "new-session" "-As" "$session_arg"
      "-e" "DF_NAMESPACE=$df_namespace"
      "-e" "DF_PLATFORM=$df_platform")
  fi

  # Run or dryrun?
  if [[ "$dry_run_arg" == "true" ]]; then
    echo "Dry run command:"
    printf '%q ' "${ssh_cmd[@]}"
    echo
    exit 0
  fi

  exec "${ssh_cmd[@]}"
}

# shellcheck disable=SC2154,SC2155
cmd_sync_check() {
  local base_dir="$HOME/projects"
  local -a needs_action=()
  local -a not_git=()

  for repo in "$base_dir"/*; do
    [ -d "$repo" ] || continue
    local git_dir="$repo/.git"

    if [ ! -d "$git_dir" ]; then
      not_git+=("$(basename "$repo")")
      continue
    fi

    echo "=== $(basename "$repo") ==="
    cd "$repo" || continue

    local action_required=0

    git fetch --all --quiet || true
    local branch
    branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")

    # --- Uncommitted or untracked changes ---
    if ! git diff --quiet || ! git diff --cached --quiet; then
      echo "Uncommitted changes:"
      git status --short
      action_required=1
    elif [ -n "$(git ls-files --others --exclude-standard)" ]; then
      echo "Untracked files:"
      git ls-files --others --exclude-standard
      action_required=1
    else
      echo "No uncommitted or untracked changes."
    fi

    # --- Unpushed commits on current branch ---
    if git rev-parse --abbrev-ref "${branch}@{u}" >/dev/null 2>&1; then
      local unpushed
      unpushed=$(git rev-list --oneline "${branch}@{u}..${branch}")
      if [ -n "$unpushed" ]; then
        echo "Unpushed commits on ${branch}:"
        echo "$unpushed"
        action_required=1
      else
        echo "No unpushed commits on ${branch}."
      fi
    else
      echo "No upstream set for ${branch}."
      action_required=1
    fi

    # --- Unpushed branches ---
    local unpushed_branches=()
    while IFS= read -r b; do
      if git rev-parse --abbrev-ref "${b}@{u}" >/dev/null 2>&1; then
        local ahead
        ahead=$(git rev-list --count "${b}@{u}..${b}")
        if [ "$ahead" -gt 0 ]; then
          unpushed_branches+=("$b ($ahead ahead)")
        fi
      else
        unpushed_branches+=("$b (no upstream)")
      fi
    done < <(git for-each-ref --format='%(refname:short)' refs/heads)

    if [ "${#unpushed_branches[@]}" -gt 0 ]; then
      echo "Unpushed branches:"
      printf '  %s\n' "${unpushed_branches[@]}"
      action_required=1
    else
      echo "No unpushed branches."
    fi

    echo
    ((action_required)) && needs_action+=("$(basename "$repo")")
  done

  echo "=== SUMMARY ==="
  if [ "${#needs_action[@]}" -gt 0 ]; then
    echo "Projects needing action:"
    printf '  %s\n' "${needs_action[@]}" | sort -u
  else
    echo "All repositories clean and synced."
  fi

  if [ "${#not_git[@]}" -gt 0 ]; then
    echo
    echo "Directories without Git repositories:"
    printf '  %s\n' "${not_git[@]}" | sort -u
  fi
}

# shellcheck disable=SC2154,SC2155
cmd_create() {
  # VARS: name_arg, image_arg, project_arg

  # Check if container name already exists
  local cname="$(get_cname)"
  if docker_container_exists "$cname"; then
    printf -v msg 'Container already exists: "%s" (from name "%s")' "$cname" "$name_arg"
    fail "$msg"
  fi

  # Check if project path is valid
  local project_path
  project_path="$(resolve_path "$project_arg")"
  if [[ ! -d "$project_path" ]]; then
    fail "Invalid project path: $project_path"
  fi

  # Check image
  IFS=' ' read -r image_ref _ _ _ <<<"$(parse_image_ref "$image_arg")"
  if ! docker_image_present "$image_ref"; then
    printf -v msg 'Image not found locally.\nTry:\n\t- docker pull %s' "$image_ref"
    fail "$msg"
  fi

  # Run (= create and start container)
  cmd=(
    docker run -d
    --name "$cname"
    --label dev=true
    --label "dev.name=$name_arg"
    --label "dev.project_path=$project_path"
    --label "dev.image_ref=$image_ref"
    --network host
    --init # run tini as PID 1 to handle signals & reap zombies for cleaner container shutdown
    -v "$project_path:/workspace"
    -v /var/run/docker.sock:/var/run/docker.sock
  )

  [[ -d "$HOME/.ssh" ]] && cmd+=(-v "$HOME/.ssh:$CONTAINER_HOME/.ssh:ro")
  [[ -f "$HOME/.npmrc" ]] && cmd+=(-v "$HOME/.npmrc:$CONTAINER_HOME/.npmrc:ro")
  [[ -d "$HOME/.npm" ]] && cmd+=(-v "$HOME/.npm:$CONTAINER_HOME/.npm")

  docker_gid="$(getent group docker | cut -d: -f3 || true)"
  [[ -n "$docker_gid" ]] && cmd+=(--group-add "$docker_gid")

  cmd+=("$image_ref" sleep infinity)
  "${cmd[@]}"

  printf "Created and started container: %s" "$cname"
}

# shellcheck disable=SC2154,SC2155
cmd_connect() {
  # VARS: name_arg

  local cname="$(get_cname)"
  if ! docker_container_exists "$cname"; then
    fail "Container does not exist: ${cname}. Run: dev create ..."
  fi

  if ! docker_container_running "$cname"; then
    docker start "$cname" >/dev/null
  fi

  if ! command -v tmux >/dev/null 2>&1; then
    echo "tmux not found; falling back to direct exec"
    exec "$0" exec "$cname"
  fi

  local image_ref
  image_ref="$(docker container inspect "$cname" --format '{{ .Config.Image }}')"
  IFS=' ' read -r _image_ref _ _ image_label <<<"$(parse_image_ref "$image_ref")"

  if ! tmux has-session -t "$cname" 2>/dev/null; then
    tmux new-session -ds "$cname" \
      -e "DF_IMAGE=$image_label" \
      -e "DF_NAMESPACE=$DF_NAMESPACE" \
      -e "DF_PLATFORM=$DF_PLATFORM" \
      "$0 exec \"$name_arg\""
    tmux set-option -t "$cname" default-command "$0 exec \"$name_arg\""
  fi

  if [[ -n "${TMUX-}" ]]; then
    tmux switch-client -t "$cname"
  else
    tmux attach -t "$cname"
  fi
}

# shellcheck disable=SC2154,SC2155
cmd_exec() {
  # VARS: name_arg, cmd_arg

  local cname="$(get_cname)"
  if ! docker_container_running "$cname"; then
    fail "Container $cname not running"
  fi

  if [[ -n "$cmd_arg" ]]; then
    if [[ -t 0 ]]; then
      docker exec -it "$cname" "${cmd_arg}"
    else
      docker exec "$cname" "${cmd_arg}"
    fi
    return
  fi

  # No command provided -> open a shell
  docker exec --detach-keys "ctrl-q,ctrl-p" -it "$cname" zsh -l ||
    docker exec --detach-keys "ctrl-q,ctrl-p" -it "$cname" bash -l ||
    docker exec --detach-keys "ctrl-q,ctrl-p" -it "$cname" sh
}

shorten_project_path() {
  local project=$1
  local home=${HOME%/}
  local projdir=${PROJECT_DIR%/}

  # Case 1: under PROJECT_DIR
  if [[ -n ${projdir} && $project == "$projdir"/* ]]; then
    # shellcheck disable=SC2088
    project="~/$PROJECT_ABBR${project#"$projdir"}"

  # Case 2: equals HOME
  elif [[ $project == "$home" ]]; then
    project="~"

  # Case 3: under HOME (but not PROJECT_DIR)
  elif [[ $project == "$home"/* ]]; then
    project="~${project#"$home"}"
  fi

  printf '%s\n' "$project"
}

# shellcheck disable=SC2154,SC2155
cmd_list() {
  # VARS:

  {
    echo "NAME|IMAGE|PROJECT|STATUS"
    docker ps -a --filter "label=dev=true" \
      --format '{{.Label "dev.name"}}|{{.Image}}|{{.Label "dev.project_path"}}|{{.Status}}'
  } | while IFS='|' read -r fname image project status; do
    # Shorten registry prefix
    image="${image/$REGISTRY\//$REGISTRY_ABBR/}"

    # Shorten project path
    project="$(shorten_project_path "$project")"

    echo "$fname|$image|$project|$status"
  done | column -t -s '|'
}

tmux_fallback_to_default_if_in_session() {
  # If inside tmux and current session matches the given one,
  # switch to or create 'default' before proceeding.
  local target_session="$1"

  [[ -z "${TMUX-}" ]] && return 0 # not in tmux, nothing to do

  local current_session
  current_session="$(tmux display-message -p '#S')"

  if [[ "$current_session" == "$target_session" ]]; then
    if ! tmux has-session -t default 2>/dev/null; then
      tmux new-session -ds default
    fi
    tmux switch-client -t default
  fi
}

# shellcheck disable=SC2154,SC2155
cmd_stop() {
  # VARS: kill_arg name_arg
  local cname
  cname="$(get_cname)"
  docker_container_exists "$cname" || fail "Container $cname does not exist"

  if [[ "$kill_arg" == "true" ]]; then
    echo "Killing container $cname..."
    docker kill "$cname"
  else
    echo "Stopping container $cname..."
    docker stop "$cname"
  fi

  tmux_fallback_to_default_if_in_session "$cname"
}

# shellcheck disable=SC2154,SC2155
cmd_remove() {
  # VARS: force_arg name_arg
  local cname
  cname="$(get_cname)"
  docker_container_exists "$cname" || fail "Container $cname does not exist"

  if [[ "$force_arg" == "true" ]]; then
    echo "Removing container $cname (force)..."
    docker rm -f "$cname"
  else
    echo "Removing container $cname..."
    docker rm "$cname"
  fi

  tmux_fallback_to_default_if_in_session "$cname"
}

# shellcheck disable=SC2154,SC2155
cmd_respawn() {
  # VARS: name_arg
  local cname
  cname="$(get_cname)"
  panes=$(tmux list-panes -t "$cname" -s -F "#{session_name}:#{window_index}.#{pane_index}")

  for pane in $panes; do
    echo "Respawning $pane..."
    tmux respawn-pane -t "$pane"
  done
}

# shellcheck disable=SC2154,SC2155
cmd_test() {
  # VARS: name_arg

  echo "Script dev is working fine!"
  if [[ -n "$name_arg" ]]; then
    get_cname
  fi
  echo
}

barg_run SPEC[@] "$@"
