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

run_id=""
repo_url="https://github.com/pikasTech/unidesk"
desired_ref="master"
manifest_commit=""
environment="dev"
result_dir=""
timeout_ms="1800000"
keep_namespace="false"
manifest_file=""

usage() {
  cat <<'EOF'
Usage:
  dev-e2e.sh --run-id ID --manifest-commit COMMIT --manifest-file FILE --result-dir DIR [--repo-url URL] [--desired-ref master] [--environment dev] [--timeout-ms MS] [--keep-namespace]

This script runs the D601 dev namespace e2e harness from a Git-controlled blob.
It must be launched by the CLI with a short command; do not paste this script
body through the maintenance channel.
EOF
}

while [ "$#" -gt 0 ]; do
  case "$1" in
    --run-id)
      run_id="${2:-}"
      shift 2
      ;;
    --repo-url)
      repo_url="${2:-}"
      shift 2
      ;;
    --desired-ref)
      desired_ref="${2:-}"
      shift 2
      ;;
    --manifest-commit)
      manifest_commit="${2:-}"
      shift 2
      ;;
    --environment)
      environment="${2:-}"
      shift 2
      ;;
    --result-dir)
      result_dir="${2:-}"
      shift 2
      ;;
    --manifest-file)
      manifest_file="${2:-}"
      shift 2
      ;;
    --timeout-ms)
      timeout_ms="${2:-}"
      shift 2
      ;;
    --keep-namespace)
      keep_namespace="true"
      shift
      ;;
    -h|--help)
      usage
      exit 0
      ;;
    *)
      echo "unknown argument: $1" >&2
      usage >&2
      exit 2
      ;;
  esac
done

if ! [[ "$run_id" =~ ^[a-z0-9]([-a-z0-9]{0,46}[a-z0-9])?$ ]]; then
  echo "invalid --run-id: $run_id" >&2
  exit 2
fi
if ! [[ "$manifest_commit" =~ ^[0-9a-f]{40}$ ]]; then
  echo "--manifest-commit must be a full 40 character SHA" >&2
  exit 2
fi
if [ "$environment" != "dev" ]; then
  echo "only --environment dev is supported" >&2
  exit 2
fi
if ! [[ "$timeout_ms" =~ ^[0-9]+$ ]] || [ "$timeout_ms" -le 0 ]; then
  echo "--timeout-ms must be a positive integer" >&2
  exit 2
fi
if [ -z "$result_dir" ]; then
  result_dir="/home/ubuntu/.unidesk/runs/$run_id"
fi
if [ -z "$manifest_file" ] || [ ! -f "$manifest_file" ]; then
  echo "--manifest-file must point to the commit-pinned deploy.json fetched by the launcher" >&2
  exit 2
fi

mkdir -p "$result_dir"
runner_log="$result_dir/runner.log"
result_json="$result_dir/result.json"
exec > >(tee -a "$runner_log") 2>&1

log_json() {
  local event="$1"
  shift || true
  printf '{"at":"%s","event":"%s"' "$(date -Iseconds)" "$event"
  while [ "$#" -gt 1 ]; do
    printf ',"%s":%s' "$1" "$(printf '%s' "$2" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read()))')"
    shift 2
  done
  printf '}\n'
}

write_result() {
  local ok="$1"
  local status="$2"
  local detail="$3"
  python3 - "$result_json" "$ok" "$status" "$detail" "$run_id" "$repo_url" "$desired_ref" "$manifest_commit" "$environment" "$pipeline_run" "$temporary_namespace" "$code_queue_image" <<'PY'
import json
import sys
from datetime import datetime, timezone

path, ok, status, detail, run_id, repo, desired_ref, commit, environment, pipeline_run, temporary_namespace, code_queue_image = sys.argv[1:]
record = {
    "ok": ok == "true",
    "status": status,
    "detail": detail,
    "runId": run_id,
    "repoUrl": repo,
    "desiredRef": desired_ref,
    "manifestCommit": commit,
    "environment": environment,
    "pipelineRun": pipeline_run or None,
    "temporaryNamespace": temporary_namespace or None,
    "codeQueueImage": code_queue_image or None,
    "finishedAt": datetime.now(timezone.utc).isoformat(),
}
with open(path, "w", encoding="utf-8") as handle:
    json.dump(record, handle, ensure_ascii=False, indent=2)
    handle.write("\n")
print(json.dumps(record, ensure_ascii=False))
PY
}

pipeline_run=""
temporary_namespace="unidesk-ci-e2e-$run_id"
code_queue_image=""
trap 'code=$?; if [ "$code" -ne 0 ] && [ ! -f "$result_json" ]; then write_result false failed "runner exited with code $code" || true; fi' EXIT

export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
if ! context=$(kubectl config current-context 2>&1); then
  echo "d601_native_k3s_guard=blocked reason=context-read-failed detail=$context" >&2
  exit 1
fi
if ! server=$(kubectl config view --minify -o 'jsonpath={.clusters[0].cluster.server}' 2>&1); then
  echo "d601_native_k3s_guard=blocked reason=server-read-failed detail=$server" >&2
  exit 1
fi
if ! nodes=$(kubectl get nodes -o 'jsonpath={range .items[*]}{.metadata.name}{"\n"}{end}' 2>&1); then
  echo "d601_native_k3s_guard=blocked reason=nodes-read-failed detail=$nodes" >&2
  exit 1
fi
if printf '%s\n%s\n%s\n' "$context" "$server" "$nodes" | grep -Eiq 'docker-desktop|desktop-control-plane|127\.0\.0\.1:11700'; then
  echo "d601_native_k3s_guard=refused reason=forbidden-control-plane context=$context server=$server" >&2
  exit 1
fi
if ! printf '%s\n' "$nodes" | grep -Fx d601 >/dev/null; then
  echo "d601_native_k3s_guard=blocked reason=missing-d601-node nodes=$(printf '%s' "$nodes" | tr '\n' ',')" >&2
  exit 1
fi
echo "d601_native_k3s_guard=pass kubeconfig=$KUBECONFIG context=$context server=$server node=d601"

log_json runner_started run_id "$run_id" manifest_commit "$manifest_commit"
kubectl get pipeline/unidesk-dev-namespace-e2e -n unidesk-ci >/dev/null
kubectl get pvc/unidesk-ci-cache -n unidesk-ci >/dev/null

service_env="$result_dir/dev-e2e-service-commits.env"
python3 - "$manifest_file" "$service_env" "$environment" <<'PY'
import json
import re
import sys

manifest_path, out_path, environment = sys.argv[1:]
with open(manifest_path, "r", encoding="utf-8") as handle:
    manifest = json.load(handle)
if manifest.get("schemaVersion") != 2:
    raise SystemExit("deploy.json must use schemaVersion=2")
env = manifest.get("environments", {}).get(environment)
if not isinstance(env, dict):
    raise SystemExit(f"deploy.json must contain environments.{environment}")
services = env.get("services")
if not isinstance(services, list) or not services:
    raise SystemExit(f"deploy.json environments.{environment}.services must contain services")
lines = []
summary = []
for service in services:
    if not isinstance(service, dict) or not service.get("id") or not service.get("repo") or not service.get("commitId"):
        raise SystemExit(f"each deploy.json environments.{environment} service must contain id, repo and commitId")
    key = re.sub(r"[^A-Z0-9]", "_", str(service["id"]).upper())
    commit = str(service["commitId"])
    lines.append(f"{key}_COMMIT={commit}")
    summary.append({"id": service["id"], "commitId": commit})
with open(out_path, "w", encoding="utf-8") as handle:
    handle.write("\n".join(lines) + "\n")
print(json.dumps({"ok": True, "environment": environment, "services": summary}, ensure_ascii=False))
PY
# shellcheck disable=SC1090
source "$service_env"
backend_commit="${BACKEND_CORE_COMMIT:-unknown}"
frontend_commit="${FRONTEND_COMMIT:-unknown}"
code_queue_commit="${CODE_QUEUE_COMMIT:-unknown}"
if ! [[ "$code_queue_commit" =~ ^[0-9a-f]{40}$ ]]; then
  echo "deploy.json environments.$environment.services must include code-queue with a full 40 character commitId; got: $code_queue_commit" >&2
  exit 2
fi
deploy_json_b64="$(base64 -w0 "$manifest_file")"

work_dir="$(dirname "$manifest_file")"
repo_dir="$work_dir/repo"
if [ ! -d "$repo_dir/.git" ]; then
  echo "launcher repo checkout is missing: $repo_dir" >&2
  exit 2
fi

root_exec() {
  if [ "$(id -u)" = "0" ]; then
    "$@"
    return
  fi
  if sudo -n true >/dev/null 2>&1; then
    sudo -n "$@"
    return
  fi
  if [ -x /mnt/c/Windows/System32/wsl.exe ]; then
    /mnt/c/Windows/System32/wsl.exe -u root -- "$@"
    return
  fi
  echo "dev_e2e_native_k3s_root_access=missing" >&2
  return 1
}

import_image_to_k3s() {
  local image="$1"
  local archive="/tmp/unidesk-ci-image-${run_id}-${image//[^A-Za-z0-9_.-]/-}.tar"
  rm -f "$archive"
  docker save "$image" -o "$archive"
  root_exec ctr --address /run/k3s/containerd/containerd.sock -n k8s.io images import --digests --all-platforms "$archive"
  rm -f "$archive"
  echo "dev_e2e_image_imported=$image"
}

ensure_runtime_image() {
  local image="$1"
  if ! docker image inspect "$image" >/dev/null 2>&1; then
    echo "dev_e2e_runtime_image_pull=$image"
    docker pull --platform linux/amd64 "$image"
  else
    echo "dev_e2e_runtime_image_cached=$image"
  fi
  import_image_to_k3s "$image"
}

build_code_queue_image() {
  local short="${code_queue_commit:0:12}"
  local commit_image="unidesk-code-queue:ci-$short"
  local run_image="unidesk-code-queue:ci-dev-e2e-$run_id"
  local source_dir="$work_dir/code-queue-src-$short"
  local resolved
  git -C "$repo_dir" fetch --no-tags origin "$code_queue_commit" || git -C "$repo_dir" fetch --no-tags origin '+refs/heads/*:refs/remotes/origin/*'
  resolved="$(git -C "$repo_dir" rev-parse --verify "$code_queue_commit^{commit}")"
  if [ "$resolved" != "$code_queue_commit" ]; then
    echo "code_queue_commit_mismatch resolved=$resolved expected=$code_queue_commit" >&2
    exit 1
  fi
  local existing_rev
  existing_rev="$(docker image inspect "$commit_image" --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' 2>/dev/null || true)"
  if [ "$existing_rev" != "$resolved" ]; then
    rm -rf "$source_dir"
    mkdir -p "$source_dir"
    git -C "$repo_dir" archive "$resolved" | tar -x -C "$source_dir"
    local base_args=()
    if docker image inspect unidesk-code-queue:d601-build-base >/dev/null 2>&1; then
      base_args=(--build-arg CODE_QUEUE_BASE_IMAGE=unidesk-code-queue:d601-build-base)
    elif docker image inspect unidesk-code-queue:d601 >/dev/null 2>&1; then
      base_args=(--build-arg CODE_QUEUE_BASE_IMAGE=unidesk-code-queue:d601)
    fi
    echo "dev_e2e_code_queue_image_build=$commit_image commit=$resolved"
    docker build \
      --network host \
      "${base_args[@]}" \
      --build-arg HTTP_PROXY="${HTTP_PROXY:-}" \
      --build-arg HTTPS_PROXY="${HTTPS_PROXY:-}" \
      --build-arg ALL_PROXY="${ALL_PROXY:-}" \
      --build-arg NO_PROXY="${NO_PROXY:-}" \
      --label "org.opencontainers.image.source=$repo_url" \
      --label "org.opencontainers.image.revision=$resolved" \
      --label "unidesk.ai/ci-run-id=$run_id" \
      --label "unidesk.ai/ci-kind=dev-e2e" \
      -t "$commit_image" \
      -f "$source_dir/src/components/microservices/code-queue/Dockerfile" \
      "$source_dir"
  else
    echo "dev_e2e_code_queue_image_cached=$commit_image commit=$resolved"
  fi
  docker tag "$commit_image" "$run_image"
  import_image_to_k3s "$run_image"
  code_queue_image="$run_image"
}

ensure_runtime_image "postgres:16-alpine"
build_code_queue_image

pipeline_manifest="$result_dir/pipelinerun.yaml"
cat >"$pipeline_manifest" <<YAML
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
  generateName: unidesk-dev-e2e-$run_id-
  namespace: unidesk-ci
  labels:
    app.kubernetes.io/name: unidesk-dev-namespace-e2e
    app.kubernetes.io/part-of: unidesk
    unidesk.ai/ci-kind: dev-namespace-e2e
    unidesk.ai/deploy-ref: master-deploy-json-dev
    unidesk.ai/deploy-commit: "$manifest_commit"
spec:
  pipelineRef:
    name: unidesk-dev-namespace-e2e
  taskRunTemplate:
    serviceAccountName: unidesk-ci-runner
  params:
    - name: repo-url
      value: "$repo_url"
    - name: desired-ref
      value: "$desired_ref"
    - name: deploy-commit
      value: "$manifest_commit"
    - name: environment
      value: "$environment"
    - name: run-id
      value: "$run_id"
    - name: keep-namespace
      value: "$keep_namespace"
    - name: backend-core-commit
      value: "$backend_commit"
    - name: frontend-commit
      value: "$frontend_commit"
    - name: code-queue-commit
      value: "$code_queue_commit"
    - name: app-image
      value: "$code_queue_image"
    - name: deploy-json-b64
      value: "$deploy_json_b64"
  workspaces:
    - name: shared-workspace
      persistentVolumeClaim:
        claimName: unidesk-ci-cache
YAML

pipeline_run="$(kubectl create -f "$pipeline_manifest" -o jsonpath='{.metadata.name}')"
printf '%s\n' "$pipeline_run" >"$result_dir/pipelinerun.txt"
log_json pipelinerun_created pipeline_run "$pipeline_run" namespace unidesk-ci

deadline=$((SECONDS + (timeout_ms + 999) / 1000))
condition=""
while [ "$SECONDS" -lt "$deadline" ]; do
  condition="$(kubectl get "pipelinerun/$pipeline_run" -n unidesk-ci -o jsonpath='{range .status.conditions[?(@.type=="Succeeded")]}{.status}{"\t"}{.reason}{"\t"}{.message}{end}' 2>/dev/null || true)"
  case "$condition" in
    True*)
      kubectl get "pipelinerun/$pipeline_run" -n unidesk-ci -o json >"$result_dir/pipelinerun.json"
      kubectl get taskrun -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" -o json >"$result_dir/taskruns.json" || true
      kubectl logs -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" --all-containers=true --tail=-1 >"$result_dir/pods.log" 2>&1 || true
      write_result true succeeded "$condition"
      exit 0
      ;;
    False*)
      kubectl get "pipelinerun/$pipeline_run" -n unidesk-ci -o json >"$result_dir/pipelinerun.json" || true
      kubectl get taskrun -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" -o json >"$result_dir/taskruns.json" || true
      kubectl logs -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" --all-containers=true --tail=-1 >"$result_dir/pods.log" 2>&1 || true
      write_result false failed "$condition"
      exit 1
      ;;
  esac
  sleep 2
done

kubectl get "pipelinerun/$pipeline_run" -n unidesk-ci -o json >"$result_dir/pipelinerun.json" || true
kubectl get taskrun -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" -o json >"$result_dir/taskruns.json" || true
kubectl logs -n unidesk-ci -l "tekton.dev/pipelineRun=$pipeline_run" --all-containers=true --tail=-1 >"$result_dir/pods.log" 2>&1 || true
write_result false timeout "Timed out waiting for pipelinerun/$pipeline_run"
exit 124
