#!/usr/bin/env bash
# Build a NDO service locally with Docker, push to artifactory, point a k8s deployment at it.
set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=lib/env.sh
source "$HERE/lib/env.sh"

REG="${NDO_REGISTRY:-[REDACTED REGISTRY]}"
ART_USER="${NDO_ARTIFACTORY_USER:-$USER}"
PLATFORM="${NDO_PLATFORM:-linux/amd64}"
PROJECTS="${NDO_PROJECTS:-$HOME/projects}"

ENV_ALIAS="${NDO_ENV:-}"
SVC=""; DIR=""; TAG=""; TICKET=""; DFILE=""; TARGET="release"
YES=0; NOCACHE=0; SKIP_TESTS=0; TIMEOUT="10m"

die() { echo "ERROR: $*" >&2; exit 1; }
say() { echo "==> $*" >&2; }

usage() {
  cat <<'USAGE'
ndo-ship.sh — local build → artifactory → k8s deploy for NDO services.

Commands:
  doctor                       check docker/buildx/registry-login/kubectl
  login                        docker login to artifactory (interactive)
  tag      <service>           print the image ref that would be built
  test     <service>           run unit tests only (maven, or docker --target test)
  build    <service>           build the image (runs unit tests first unless --skip-tests)
  push     <service>           push the last built (or --tag'd) image
  deploy   <service> -e ENV    point <service>-v1 at the image + wait for rollout  [needs --yes]
  ship     <service> -e ENV    test → build → push → deploy → rollout wait          [needs --yes]
  status   <service> -e ENV    deployed image, replicas, pod state
  rollback <service> -e ENV    restore the image recorded before the last deploy   [needs --yes]
  pullsecret <service> -e ENV  attach local docker creds as an imagePullSecret (ImagePullBackOff fix) [needs --yes]

Options:
  -e, --env ALIAS     target env (see: ndo-api.sh env ls). Ambiguous short names are rejected.
  -t, --tag TAG       image tag (default: UTC timestamp, always unique)
      --ticket N      UNM number for the repo name (default: parsed from git branch)
  -d, --dir PATH      service repo (default: $NDO_PROJECTS/<service>)
  -f, --file FILE     dockerfile (default: Dockerfile_local, falls back to Dockerfile)
      --target STAGE  build target (default: release; ignored if the dockerfile has no stages)
      --platform P    default linux/amd64 — do NOT drop this on an arm64 Mac
      --skip-tests    skip unit tests in build/ship
      --no-cache      docker build --no-cache
      --timeout D     rollout wait (default 10m)
  -y, --yes           confirm a cluster-mutating command (deploy/ship/rollback/pullsecret)

Image ref: $REG/<[REDACTED USER]>/<service>_unm_<ticket>:<tag>
Env overrides: NDO_REGISTRY NDO_ARTIFACTORY_USER NDO_PLATFORM NDO_PROJECTS NDO_ENV
USAGE
}

parse_opts() {
  while [ $# -gt 0 ]; do
    case "$1" in
      -e|--env)      ENV_ALIAS="$2"; shift 2 ;;
      -t|--tag)      TAG="$2"; shift 2 ;;
      --ticket)      TICKET="$2"; shift 2 ;;
      -d|--dir)      DIR="$2"; shift 2 ;;
      -f|--file)     DFILE="$2"; shift 2 ;;
      --target)      TARGET="$2"; shift 2 ;;
      --platform)    PLATFORM="$2"; shift 2 ;;
      --timeout)     TIMEOUT="$2"; shift 2 ;;
      --skip-tests)  SKIP_TESTS=1; shift ;;
      --no-cache)    NOCACHE=1; shift ;;
      -y|--yes)      YES=1; shift ;;
      -*)            die "unknown option $1" ;;
      *)             [ -z "$SVC" ] && SVC="$1" || die "unexpected arg $1"; shift ;;
    esac
  done
}

need_svc() { [ -n "$SVC" ] || die "no service given"; }

svc_dir() {
  need_svc
  [ -n "$DIR" ] || DIR="$PROJECTS/$SVC"
  [ -d "$DIR" ] || die "service repo not found: $DIR (use --dir)"
  echo "$DIR"
}

dockerfile() {
  local d; d="$(svc_dir)"
  if [ -n "$DFILE" ]; then [ -f "$d/$DFILE" ] || [ -f "$DFILE" ] || die "dockerfile not found: $DFILE"; echo "$DFILE"; return; fi
  if [ -f "$d/Dockerfile_local" ]; then echo "Dockerfile_local"; return; fi
  echo "Dockerfile"
  echo "no Dockerfile_local in $d — using Dockerfile. If the build pulls shared/external artifacts, create Dockerfile_local (see reference/dockerfile-local.md)." >&2
}

ticket() {
  [ -n "$TICKET" ] && { echo "$TICKET"; return; }
  local d b; d="$(svc_dir)"
  b=$(git -C "$d" branch --show-current 2>/dev/null || true)
  if [[ "$b" =~ [Uu][Nn][Mm][-_]?([0-9]+) ]]; then echo "${BASH_REMATCH[1]}"; else echo "local"; fi
}

image_ref() {
  need_svc
  local t; t="${TAG:-$(date -u +%Y%m%d-%H%M%S)}"
  echo "$REG/$ART_USER/${SVC}_unm_$(ticket):$t"
}

last_image_file() { mkdir -p "$NDO_CACHE/last-image"; echo "$NDO_CACHE/last-image/$SVC"; }

resolve_image() {
  if [ -n "$TAG" ]; then image_ref; return; fi
  local f; f="$(last_image_file)"
  [ -s "$f" ] || die "no image built yet for $SVC — run 'build' first or pass --tag"
  cat "$f"
}

confirm() {
  [ "$YES" -eq 1 ] || die "'$1' mutates shared env '$ENV_ALIAS' (context $NDO_CTX, ns $NDO_NS). Re-run with --yes once the user has approved."
}

container_name() {
  local names first
  names=$(kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
            -o jsonpath='{range .spec.template.spec.containers[*]}{.name}{"\n"}{end}')
  if grep -qx "$SVC" <<<"$names"; then echo "$SVC"; else first=$(head -1 <<<"$names"); [ -n "$first" ] || die "no containers in $SVC-v1"; echo "$first"; fi
}

current_image() {
  kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
    -o jsonpath='{.spec.template.spec.containers[0].image}'
}

rollback_file() { mkdir -p "$NDO_CACHE/rollback"; echo "$NDO_CACHE/rollback/$(tr '/' '_' <<<"$ENV_ALIAS")__$SVC"; }

is_maven()  { [ -f "$(svc_dir)/pom.xml" ]; }
is_go()     { [ -f "$(svc_dir)/go.mod" ]; }
has_stages() { grep -qiE '^[[:space:]]*FROM .* AS ' "$(svc_dir)/$(dockerfile)"; }
copies_target() { grep -qE 'COPY .*target/' "$(svc_dir)/$(dockerfile)"; }

mvn_env() {
  export JAVA_HOME="${JAVA_HOME:-/Library/Java/JavaVirtualMachines/jdk-25.0.2.jdk/Contents/Home}"
  export PATH="$JAVA_HOME/bin:$PATH"
}

run_tests() {
  local d; d="$(svc_dir)"
  if is_maven; then
    say "maven unit tests ($SVC)"
    ( mvn_env; cd "$d" && mvn -B test )
  elif is_go && grep -qiE '^[[:space:]]*FROM .* AS test' "$d/$(dockerfile)"; then
    say "docker test stage ($SVC)"
    docker build --platform "$PLATFORM" -f "$d/$(dockerfile)" --target test -t "$SVC-test:local" "$d"
  elif is_go; then
    say "go test ($SVC)"
    ( cd "$d" && go test ./... )
  else
    say "no unit-test runner detected for $SVC — skipping"
  fi
}

do_build() {
  local d df img args=()
  d="$(svc_dir)"; df="$(dockerfile)"; img="$(image_ref)"
  [ "$SKIP_TESTS" -eq 1 ] || run_tests
  # Java services copy target/*.jar into the image — package first.
  if is_maven && copies_target; then
    say "mvn package -DskipTests (jar for the image layer)"
    ( mvn_env; cd "$d" && mvn -B -DskipTests package )
  fi
  args=(build --platform "$PLATFORM" -f "$d/$df" -t "$img")
  has_stages && grep -qiE "^[[:space:]]*FROM .* AS $TARGET\$" "$d/$df" && args+=(--target "$TARGET")
  [ "$NOCACHE" -eq 1 ] && args+=(--no-cache)
  args+=("$d")
  say "docker ${args[*]}"
  docker "${args[@]}"
  echo "$img" > "$(last_image_file)"
  echo "$img"
}

do_push() {
  local img; img="$(resolve_image)"
  say "docker push $img"
  docker push "$img"
  echo "$img"
}

do_deploy() {
  local img c prev
  env_resolve "$ENV_ALIAS"
  confirm deploy
  img="$(resolve_image)"
  c="$(container_name)"
  prev="$(current_image)"
  echo "$prev" > "$(rollback_file)"
  say "rollback point saved: $prev"
  say "set image $SVC-v1/$c=$img  (ctx=$NDO_CTX ns=$NDO_NS)"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$img"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT" || {
    echo "--- rollout failed; pod events ---" >&2
    kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
      -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\t"}{range .status.containerStatuses[*]}{.state}{end}{"\n"}{end}' >&2
    echo "ImagePullBackOff => node has no creds for $REG. Fix: ndo-ship.sh pullsecret $SVC -e $ENV_ALIAS --yes" >&2
    return 1
  }
  do_status
}

do_status() {
  env_resolve "$ENV_ALIAS"
  need_svc
  echo "env      : $ENV_ALIAS  (ctx=$NDO_CTX ns=$NDO_NS)"
  echo "image    : $(current_image)"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy "$SVC-v1" \
    -o custom-columns='READY:.status.readyReplicas,DESIRED:.spec.replicas,UPDATED:.status.updatedReplicas'
  kubectl --context="$NDO_CTX" -n "$NDO_NS" get pod -l "app=$SVC-v1" \
    -o custom-columns='POD:.metadata.name,PHASE:.status.phase,RESTARTS:.status.containerStatuses[0].restartCount,AGE:.metadata.creationTimestamp'
}

do_rollback() {
  local f prev c
  env_resolve "$ENV_ALIAS"
  confirm rollback
  f="$(rollback_file)"
  [ -s "$f" ] || die "no rollback point recorded for $SVC on $ENV_ALIAS"
  prev="$(cat "$f")"; c="$(container_name)"
  say "restoring $prev"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" set image "deploy/$SVC-v1" "$c=$prev"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}

do_pullsecret() {
  env_resolve "$ENV_ALIAS"
  confirm pullsecret
  local sec=ndo-repro-artifactory pw
  pw=$(printf '%s' "$REG" | docker-credential-osxkeychain get 2>/dev/null \
       | python3 -c 'import sys,json;print(json.load(sys.stdin)["Secret"])') || die "no local docker creds for $REG — run: ndo-ship.sh login"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" create secret docker-registry "$sec" \
    --docker-server="$REG" --docker-username="$ART_USER" --docker-password="$pw" \
    --dry-run=client -o yaml | kubectl --context="$NDO_CTX" -n "$NDO_NS" apply -f -
  unset pw
  kubectl --context="$NDO_CTX" -n "$NDO_NS" patch deploy "$SVC-v1" \
    -p "{\"spec\":{\"template\":{\"spec\":{\"imagePullSecrets\":[{\"name\":\"$sec\"}]}}}}"
  kubectl --context="$NDO_CTX" -n "$NDO_NS" rollout status "deploy/$SVC-v1" --timeout="$TIMEOUT"
}

do_doctor() {
  printf 'docker      : %s\n' "$(docker version --format '{{.Server.Version}}' 2>&1 | head -1)"
  printf 'context     : %s\n' "$(docker context show 2>/dev/null)"
  printf 'buildx      : %s\n' "$(docker buildx version 2>&1 | head -1)"
  printf 'host arch   : %s (build platform %s)\n' "$(uname -m)" "$PLATFORM"
  if printf '%s' "$REG" | docker-credential-osxkeychain get >/dev/null 2>&1; then
    printf 'registry    : logged in to %s as %s\n' "$REG" "$ART_USER"
  else
    printf 'registry    : NOT logged in to %s — run: ndo-ship.sh login\n' "$REG"
  fi
  printf 'envs        : %s\n' "$(awk -F'\t' '!/^#/&&NF>=4' "$(env_file)" | wc -l | tr -d ' ') registered"
  [ -n "$ENV_ALIAS" ] && { env_resolve "$ENV_ALIAS"; printf 'env %-10s: ctx=%s ns=%s\n  gw=%s\n' "$ENV_ALIAS" "$NDO_CTX" "$NDO_NS" "$NDO_GW"; \
    kubectl --context="$NDO_CTX" -n "$NDO_NS" get deploy -o name >/dev/null 2>&1 \
      && echo 'kube access : ok' || echo 'kube access : FAILED (VPN down or creds expired)'; }
  return 0
}

CMD="${1:-}"; shift || true
case "$CMD" in
  doctor)     parse_opts "$@"; do_doctor ;;
  login)      docker login "$REG" ;;
  tag)        parse_opts "$@"; image_ref ;;
  test)       parse_opts "$@"; run_tests ;;
  build)      parse_opts "$@"; do_build ;;
  push)       parse_opts "$@"; do_push ;;
  deploy)     parse_opts "$@"; do_deploy ;;
  status)     parse_opts "$@"; do_status ;;
  rollback)   parse_opts "$@"; do_rollback ;;
  pullsecret) parse_opts "$@"; do_pullsecret ;;
  ship)       parse_opts "$@"; env_resolve "$ENV_ALIAS"; confirm ship
              do_build >/dev/null; TAG=""; do_push >/dev/null; do_deploy ;;
  ""|-h|--help|help) usage ;;
  *)          die "unknown command: $CMD (see --help)" ;;
esac
