# Image URL to use all building/pushing image targets
IMG ?= controller:latest

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
GOBIN=$(shell go env GOPATH)/bin
else
GOBIN=$(shell go env GOBIN)
endif

# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
# tools. (i.e. podman)
CONTAINER_TOOL ?= docker

# Setting SHELL to bash allows bash commands to be executed by recipes.
# Options are set to exit when a recipe line exits non-zero or a piped command fails.
SHELL = /usr/bin/env bash -o pipefail
.SHELLFLAGS = -ec

.PHONY: all
all: build

##@ General

# The help target prints out all targets with their descriptions organized
# beneath their categories. The categories are represented by '##@' and the
# target descriptions by '##'. The awk command is responsible for reading the
# entire set of makefiles included in this invocation, looking for lines of the
# file as xyz: ## something, and then pretty-format the target and help. Then,
# if there's a line with ##@ something, that gets pretty-printed as a category.
# More info on the usage of ANSI control characters for terminal formatting:
# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
# More info on the awk command:
# http://linuxcommand.org/lc3_adv_awk.php

.PHONY: help
help: ## Display this help.
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

.PHONY: manifests
manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
	"$(CONTROLLER_GEN)" rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
	@./hack/sync-chart-crds.sh

.PHONY: generate
generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
	"$(CONTROLLER_GEN)" object:headerFile="hack/boilerplate.go.txt" paths="./..."

.PHONY: fmt
fmt: ## Run go fmt against code.
	go fmt ./...

.PHONY: vet
vet: ## Run go vet against code.
	go vet ./...

IMAGE_TAG_BASE ?= ghcr.io/thc1006/ntn-operators
VERSION ?= 0.7.0
BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)

.PHONY: bundle
bundle: manifests ## Sync CRDs into OLM bundle manifests.
	@# Remove stale CRDs before copying (handles renames/deletions).
	@for f in bundle/manifests/*.yaml; do \
		[ -e "$$f" ] || continue; \
		if grep -q '^kind: CustomResourceDefinition$$' "$$f"; then \
			rm -f "$$f"; \
		fi; \
	done
	cp config/crd/bases/*.yaml bundle/manifests/

.PHONY: bundle-verify-sync
bundle-verify-sync: ## Verify OLM bundle CRD copies match config/crd/bases/ (CI drift check).
	@set -e; \
	SRC=config/crd/bases; DST=bundle/manifests; drift=0; \
	for src in $$SRC/ntn.operators.dev_*.yaml; do \
	  base=$$(basename "$$src"); \
	  if ! diff -q "$$src" "$$DST/$$base" >/dev/null 2>&1; then \
	    echo "DRIFT: $$DST/$$base differs from $$src (run 'make bundle')"; drift=1; \
	  fi; \
	done; \
	if [ $$drift -eq 0 ]; then echo "OLM bundle CRDs are in sync with config/crd/bases/"; else exit 1; fi

.PHONY: bundle-build
bundle-build: bundle ## Build the OLM bundle image.
	$(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) .

.PHONY: bundle-push
bundle-push: bundle-build ## Push the OLM bundle image.
	$(CONTAINER_TOOL) push $(BUNDLE_IMG)

.PHONY: bench
bench: ## Run Go benchmarks with memory allocation stats.
	go test -bench=. -benchmem -benchtime=3s -run='^$$' ./pkg/...

.PHONY: test
test: manifests generate fmt vet setup-envtest ## Run tests.
	KUBEBUILDER_ASSETS="$(shell "$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out

.PHONY: test-race
test-race: manifests generate fmt vet setup-envtest ## Run the controller + pkg suites under the race detector.
	KUBEBUILDER_ASSETS="$(shell "$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path)" go test -race -count=1 ./internal/controller/... ./pkg/...

# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
# CertManager is installed by default; skip with:
# - CERT_MANAGER_INSTALL_SKIP=true
KIND_CLUSTER ?= ntn-operators-test-e2e

.PHONY: setup-test-e2e
setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
	@command -v $(KIND) >/dev/null 2>&1 || { \
		echo "Kind is not installed. Please install Kind manually."; \
		exit 1; \
	}
	@case "$$($(KIND) get clusters)" in \
		*"$(KIND_CLUSTER)"*) \
			echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \
		*) \
			echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \
			$(KIND) create cluster --name $(KIND_CLUSTER) ;; \
	esac

.PHONY: test-e2e
test-e2e: manifests generate fmt vet ## Run the e2e tests. Defaults to a Kind environment; set E2E_USE_EXISTING_CLUSTER=true (and MANAGER_IMAGE) to target an existing cluster instead.
	@if [ -z "$$E2E_USE_EXISTING_CLUSTER" ]; then $(MAKE) setup-test-e2e; \
	else echo "E2E_USE_EXISTING_CLUSTER set — skipping Kind setup"; fi
	KIND=$(KIND) KIND_CLUSTER=$(KIND_CLUSTER) go test -tags=e2e -count=1 -timeout 15m ./test/e2e/ -v -ginkgo.v -ginkgo.fail-on-pending -ginkgo.fail-on-empty -ginkgo.json-report=/tmp/e2e-report.json
	@if [ -z "$$E2E_USE_EXISTING_CLUSTER" ]; then $(MAKE) cleanup-test-e2e; \
	else echo "E2E_USE_EXISTING_CLUSTER set — skipping Kind teardown"; fi

.PHONY: test-e2e-ha
test-e2e-ha: ## Run the HA E2E suite (#230 + durable-cache outage continuity) against an ALREADY-DEPLOYED chart release (2 replicas, leader election). Set HA_NAMESPACE (default ntn-operators-system). Does NOT create a cluster or deploy — run `make helm-deploy` first. Timeout is 30m: TestHAOutageContinuityAcrossFailover must observe continuity BEYOND propagationEpochLead (5m) across a real leader failover.
	go test -tags=e2e_ha -count=1 ./test/e2e/ -run '^TestHA' -v -timeout 30m

.PHONY: cleanup-test-e2e
cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests
	@$(KIND) delete cluster --name $(KIND_CLUSTER)

.PHONY: lint
lint: golangci-lint ## Run golangci-lint linter
	"$(GOLANGCI_LINT)" run

.PHONY: lint-fix
lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
	"$(GOLANGCI_LINT)" run --fix

.PHONY: lint-config
lint-config: golangci-lint ## Verify golangci-lint linter configuration
	"$(GOLANGCI_LINT)" config verify

.PHONY: check-action-shas
check-action-shas: ## Verify all GitHub Actions refs in .github/workflows are SHA-pinned (and resolve when GH_TOKEN is set).
	./hack/check-action-shas.sh

.PHONY: adr-lint
adr-lint: ## Validate docs/adr: front-matter metadata + unique numbers (check_adr_metadata.py) and index membership + repo-internal link/anchor resolution (check-adr-index.sh).
	python3 checks/check_adr_metadata.py docs/adr
	./hack/check-adr-index.sh

##@ Build

.PHONY: build
build: manifests generate fmt vet ## Build manager binary.
	go build -o bin/manager cmd/main.go

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
	go run ./cmd/main.go --leader-elect=false

# If you wish to build the manager image targeting other platforms you can use the --platform flag.
# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
.PHONY: docker-build
docker-build: ## Build docker image with the manager.
	$(CONTAINER_TOOL) build -t ${IMG} .

.PHONY: docker-push
docker-push: ## Push docker image with the manager.
	$(CONTAINER_TOOL) push ${IMG}

# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail)
# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
.PHONY: docker-buildx
docker-buildx: ## Build and push docker image for the manager for cross-platform support
	# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
	sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
	- $(CONTAINER_TOOL) buildx create --name ntn-operators-builder
	$(CONTAINER_TOOL) buildx use ntn-operators-builder
	- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
	- $(CONTAINER_TOOL) buildx rm ntn-operators-builder
	rm Dockerfile.cross

.PHONY: build-installer
build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
	mkdir -p dist
	cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
	"$(KUSTOMIZE)" build config/default > dist/install.yaml

##@ Deployment

ifndef ignore-not-found
  ignore-not-found = false
endif

.PHONY: install
install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
	@out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
	if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" apply -f -; else echo "No CRDs to install; skipping."; fi

.PHONY: uninstall
uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
	@out="$$( "$(KUSTOMIZE)" build config/crd 2>/dev/null || true )"; \
	if [ -n "$$out" ]; then echo "$$out" | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -; else echo "No CRDs to delete; skipping."; fi

.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
	cd config/manager && "$(KUSTOMIZE)" edit set image controller=${IMG}
	"$(KUSTOMIZE)" build config/default | "$(KUBECTL)" apply -f -

.PHONY: undeploy
undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
	"$(KUSTOMIZE)" build config/default | "$(KUBECTL)" delete --ignore-not-found=$(ignore-not-found) -f -

##@ Nephio

# kpt binary path — prefer PATH. `make nephio-install-tools` installs into
# $(go env GOPATH)/bin; ensure that directory is on your PATH.
KPT ?= kpt
KPT_VERSION ?= v1.0.0-beta.65
NEPHIO_PKGS := nephio/packages/ntn-operators-crds nephio/packages/ntn-workloads-sample

.PHONY: nephio-install-tools
nephio-install-tools: ## Install kpt CLI (Linux/macOS amd64) into $(GOPATH)/bin.
	@set -e; \
	BIN_DIR="$$(go env GOPATH 2>/dev/null)/bin"; \
	[ -n "$${BIN_DIR}" ] || { echo "go env GOPATH failed; is Go installed?"; exit 1; }; \
	mkdir -p "$${BIN_DIR}"; \
	if command -v kpt >/dev/null 2>&1; then \
	  echo "kpt already on PATH: $$(command -v kpt) ($$(kpt version 2>/dev/null | head -1))"; \
	  exit 0; \
	fi; \
	OS=$$(uname -s | tr A-Z a-z); ARCH=amd64; \
	URL="https://github.com/kptdev/kpt/releases/download/$(KPT_VERSION)/kpt_$${OS}_$${ARCH}"; \
	echo "Downloading kpt $(KPT_VERSION) from $${URL}"; \
	curl -sSL -o "$${BIN_DIR}/kpt" "$${URL}"; \
	chmod +x "$${BIN_DIR}/kpt"; \
	echo "Installed $${BIN_DIR}/kpt ($$("$${BIN_DIR}/kpt" version 2>/dev/null | head -1))"; \
	echo "If 'kpt' is not on your PATH, run: export PATH=\"$${BIN_DIR}:\$$PATH\""

.PHONY: nephio-sync
nephio-sync: manifests ## Sync CRDs from config/crd/bases/ into the Nephio ntn-operators-crds package.
	@set -e; \
	SRC=config/crd/bases; DST=nephio/packages/ntn-operators-crds; \
	for src in $$SRC/ntn.operators.dev_*.yaml; do \
	  base=$$(basename "$$src" | sed 's/^ntn\.operators\.dev_//' | sed 's/\.yaml$$/-crd.yaml/'); \
	  cp "$$src" "$$DST/$$base"; \
	  echo "  synced $$src -> $$DST/$$base"; \
	done

.PHONY: nephio-render
nephio-render: ## Run 'kpt fn render' on both Nephio packages.
	@set -e; \
	for pkg in $(NEPHIO_PKGS); do \
	  echo "==> $$pkg"; \
	  $(KPT) fn render "$$pkg"; \
	done

.PHONY: nephio-validate
nephio-validate: ## Run the Nephio package validation suite (test/nephio/validate.sh).
	@bash test/nephio/validate.sh

.PHONY: nephio-verify-sync
nephio-verify-sync: ## Verify CRD package copies match config/crd/bases/ (CI drift check).
	@set -e; \
	SRC=config/crd/bases; DST=nephio/packages/ntn-operators-crds; drift=0; \
	for src in $$SRC/ntn.operators.dev_*.yaml; do \
	  base=$$(basename "$$src" | sed 's/^ntn\.operators\.dev_//' | sed 's/\.yaml$$/-crd.yaml/'); \
	  if ! diff -q "$$src" "$$DST/$$base" >/dev/null 2>&1; then \
	    echo "DRIFT: $$DST/$$base differs from $$src"; drift=1; \
	  fi; \
	done; \
	if [ $$drift -eq 0 ]; then echo "Nephio CRD package is in sync with config/crd/bases/"; else exit 1; fi

##@ Dependencies

## Location to install dependencies to
LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
	mkdir -p "$(LOCALBIN)"

## Tool Binaries
KUBECTL ?= kubectl
KIND ?= kind
KUSTOMIZE ?= $(LOCALBIN)/kustomize
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
ENVTEST ?= $(LOCALBIN)/setup-envtest
GOLANGCI_LINT = $(LOCALBIN)/golangci-lint

## Tool Versions
KUSTOMIZE_VERSION ?= v5.8.1
CONTROLLER_TOOLS_VERSION ?= v0.21.0

#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20)
ENVTEST_VERSION ?= $(shell v='$(call gomodver,sigs.k8s.io/controller-runtime)'; \
  [ -n "$$v" ] || { echo "Set ENVTEST_VERSION manually (controller-runtime replace has no tag)" >&2; exit 1; }; \
  printf '%s\n' "$$v" | sed -E 's/^v?([0-9]+)\.([0-9]+).*/release-\1.\2/')

#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31)
ENVTEST_K8S_VERSION ?= $(shell v='$(call gomodver,k8s.io/api)'; \
  [ -n "$$v" ] || { echo "Set ENVTEST_K8S_VERSION manually (k8s.io/api replace has no tag)" >&2; exit 1; }; \
  printf '%s\n' "$$v" | sed -E 's/^v?[0-9]+\.([0-9]+).*/1.\1/')

GOLANGCI_LINT_VERSION ?= v2.12.2
.PHONY: kustomize
kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
	$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))

.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
	$(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))

.PHONY: setup-envtest
setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory.
	@echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..."
	@"$(ENVTEST)" use $(ENVTEST_K8S_VERSION) --bin-dir "$(LOCALBIN)" -p path || { \
		echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \
		exit 1; \
	}

.PHONY: envtest
envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
$(ENVTEST): $(LOCALBIN)
	$(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))

.PHONY: golangci-lint
golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
$(GOLANGCI_LINT): $(LOCALBIN)
	$(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION))
	@test -f .custom-gcl.yml && { \
		echo "Building custom golangci-lint with plugins..." && \
		$(GOLANGCI_LINT) custom --destination $(LOCALBIN) --name golangci-lint-custom && \
		mv -f $(LOCALBIN)/golangci-lint-custom $(GOLANGCI_LINT); \
	} || true

# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
# $1 - target path with name of binary
# $2 - package url which can be installed
# $3 - specific version of package
define go-install-tool
@[ -f "$(1)-$(3)" ] && [ "$$(readlink -- "$(1)" 2>/dev/null)" = "$(1)-$(3)" ] || { \
set -e; \
package=$(2)@$(3) ;\
echo "Downloading $${package}" ;\
rm -f "$(1)" ;\
GOBIN="$(LOCALBIN)" go install $${package} ;\
mv "$(LOCALBIN)/$$(basename "$(1)")" "$(1)-$(3)" ;\
} ;\
ln -sf "$$(realpath "$(1)-$(3)")" "$(1)"
endef

define gomodver
$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)
endef

##@ Documentation

CRDOC ?= $(LOCALBIN)/crdoc

.PHONY: docs
docs: ## Generate API reference documentation from CRDs.
	@command -v $(CRDOC) >/dev/null 2>&1 || GOBIN="$(LOCALBIN)" go install fybrik.io/crdoc@v0.6.4
	$(CRDOC) --resources config/crd/bases --output docs/api-reference.md

##@ ko Build

KO ?= ko
KO_DOCKER_REPO ?= ghcr.io/thc1006/ntn-operators

.PHONY: ko-build
ko-build: ## Build container image with ko (local, single-arch).
	KO_DOCKER_REPO=$(KO_DOCKER_REPO) $(KO) build ./cmd/ --bare --local

.PHONY: ko-push
ko-push: ## Build and push multi-arch image with ko.
	KO_DOCKER_REPO=$(KO_DOCKER_REPO) $(KO) build ./cmd/ --bare --platform=linux/amd64,linux/arm64

##@ Helm Deployment

## Helm binary to use for deploying the chart
HELM ?= $(LOCALBIN)/helm
## Namespace to deploy the Helm release
HELM_NAMESPACE ?= ntn-operators-system
## Name of the Helm release
HELM_RELEASE ?= ntn-operators
## Path to the Helm chart directory
HELM_CHART_DIR ?= dist/chart
## Additional arguments to pass to helm commands
HELM_EXTRA_ARGS ?=

HELM_VERSION ?= v3.21.2
.PHONY: install-helm
install-helm: $(LOCALBIN) ## Install Helm (pinned version).
	@{ command -v "$(HELM)" > /dev/null 2>&1 && "$(HELM)" version --short 2>/dev/null | grep -q "$(HELM_VERSION)"; } || { \
		HELM_OS=$$(uname -s | tr '[:upper:]' '[:lower:]') && \
		HELM_ARCH=$$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \
		HELM_TMPDIR=$$(mktemp -d) && \
		trap "rm -rf \"$${HELM_TMPDIR}\"" EXIT && \
		echo "Installing Helm $(HELM_VERSION) ($${HELM_OS}/$${HELM_ARCH})..." && \
		curl -fsSL -o "$${HELM_TMPDIR}/helm.tar.gz" "https://get.helm.sh/helm-$(HELM_VERSION)-$${HELM_OS}-$${HELM_ARCH}.tar.gz" && \
		curl -fsSL -o "$${HELM_TMPDIR}/helm.tar.gz.sha256" "https://get.helm.sh/helm-$(HELM_VERSION)-$${HELM_OS}-$${HELM_ARCH}.tar.gz.sha256" && \
		echo "$$(cat $${HELM_TMPDIR}/helm.tar.gz.sha256)  $${HELM_TMPDIR}/helm.tar.gz" | sha256sum -c - && \
		tar -xzf "$${HELM_TMPDIR}/helm.tar.gz" -C "$${HELM_TMPDIR}" && \
		mv "$${HELM_TMPDIR}/$${HELM_OS}-$${HELM_ARCH}/helm" "$(HELM)"; \
	}

.PHONY: helm-deploy
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG=repo:tag.
	$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
		--namespace $(HELM_NAMESPACE) \
		--create-namespace \
		--set manager.image.repository=$${IMG%:*} \
		--set manager.image.tag=$${IMG##*:} \
		--wait \
		--timeout 5m \
		$(HELM_EXTRA_ARGS)

.PHONY: helm-uninstall
helm-uninstall: ## Uninstall the Helm release from the K8s cluster.
	$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-status
helm-status: ## Show Helm release status.
	$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-history
helm-history: ## Show Helm release history.
	$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-rollback
helm-rollback: ## Rollback to previous Helm release.
	$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: test-e2e-wss
test-e2e-wss: ## Run the credentialed runtime-push E2E (#329, tag e2e_wss) against an ALREADY-DEPLOYED chart release. Needs NO image build: the gNB stand-in is stdlib Go run from a ConfigMap with the stock golang image, and the proxy is stock nginx — so this works on Kind and on a plain kubeadm cluster alike. Set WSS_MANAGER_NAMESPACE if the manager is not in ntn-operators-system.
	E2E_WSS_DESTRUCTIVE=1 go test -tags=e2e_wss -count=1 ./test/e2e/ -run '^TestWSSCredentialedPush' -v -timeout 20m
