#! /usr/bin/env bash

set -o errexit
set -o nounset
set -o pipefail

usage () {
	cat << EOF
USAGE: $0 [options...]
 -r, --recreate		When set, the cluster will be destroyed and recreated if it already exists
 --metallb		    When set, ensures metallb is installed in the cluster
	Optional subnet number will configure the IPAddresPool to not conflict with other clusters (--metallb 8)
 -n, --name		    Name of the cluster to create [default skupper-dev]
 -i, --images	    Source of skupper images
	One of docker, podman, ociarchive, none [default docker]
 -c, --container    Container platform to use
	One of docker, podman [default docker]
 -v, --verbose      Produces verbose output 
EOF
}

readonly KIND=${KIND:-kind}
readonly KUBECTL=${KUBECTL:-kubectl}
readonly HELM=${HELM:-helm}
readonly PYTHON=${PYTHON:-python3}
readonly IMAGE_TAG=${IMAGE_TAG:-v2-dev}
readonly ROUTER_IMAGE_TAG=${SKUPPER_ROUTER_IMAGE_TAG:-main}
readonly REGISTRY=${REGISTRY:-quay.io/skupper}
readonly SKUPPER_ROUTER_IMAGE=${SKUPPER_ROUTER_IMAGE:-${REGISTRY}/skupper-router:${ROUTER_IMAGE_TAG}}
readonly SKUPPER_CONTROLLER_IMAGE=${SKUPPER_CONTROLLER_IMAGE:-${REGISTRY}/controller:${IMAGE_TAG}}
readonly SKUPPER_KUBE_ADAPTOR_IMAGE=${SKUPPER_KUBE_ADAPTOR_IMAGE:-${REGISTRY}/kube-adaptor:${IMAGE_TAG}}
readonly SKUPPER_NETWORK_OBSERVER_IMAGE=${SKUPPER_NETWORK_OBSERVER_IMAGE:-${REGISTRY}/network-observer:${IMAGE_TAG}}
CONTAINER=docker
KIND_LOG_LEVEL="1"
VERBOSE=${VERBOSE:=false}
CLUSTER="skupper-dev"
IMAGE_SOURCE="docker"
FORCE_RECREATE="false"
METALLB="false"
SUBNET="1"

verbose_log() {
	if [ "${VERBOSE}" == "true" ]; then
		echo "(skdev) $1"
	fi
}

ensure::kind() {
	if ! command -v "${KIND}" > /dev/null 2>&1; then
		echo "(skdev) ${KIND} not found, exiting";
		echo "See https://kind.sigs.k8s.io/ for installation and usage.";
		exit 1
	else 
	   verbose_log "Found Kind ..."
	fi
}

ensure::container() {
	if [[ "$CONTAINER" == "docker" ]]; then
		if ! command -v docker > /dev/null 2>&1; then
			echo "(skdev) docker not found, exiting"
			exit 1
		else
		  	DOCKER_VERSION=$(docker --version)
			verbose_log "docker found. Version: $DOCKER_VERSION"
		fi
	elif [[ "$CONTAINER" == "podman" ]]; then
		if command -v podman > /dev/null 2>&1; then
			# Podman is found. Get and print its version and set KIND_EXPERIMENTAL_PROVIDER
			PODMAN_VERSION=$(podman --version)
			verbose_log "podman found. Version: $PODMAN_VERSION"
			export KIND_EXPERIMENTAL_PROVIDER=podman
		else
			# Podman is not found. Print error and exit.
			echo "(skdev) podman not found, exiting"
			exit 1
		fi
	else
	    echo "Invalid container specified, must be docker or podman"
		exit 1
	fi
}

ensure::helm() {
	if ! command -v "${HELM}" > /dev/null 2>&1; then
		verbose_log "This tool uses helm to enable some features. See https://helm.sh/ for installation.";
		echo "${HELM} not found, exiting";
		exit 1
	fi
}
ensure::python() {
	if ! command -v "${PYTHON}" > /dev/null 2>&1; then
		echo "This tool uses python3 for munging subnet addresses for installing metallb.";
		echo "${PYTHON} not found, exiting";
		exit 1
	else
	    verbose_log "Python version is $(python --version)"
	fi
}
kind::cluster::list() {
    ${KIND} get clusters
}

kind::cluster::delete() {
    ${KIND} delete cluster \
        --name "$1"
}

kind::cluster::create() {
    ${KIND} create cluster \
		--verbosity="${KIND_LOG_LEVEL}" \
        --name "$1"
}

kind::imageload::docker() {
	for image in "${SKUPPER_CONTROLLER_IMAGE}" \
		"${SKUPPER_KUBE_ADAPTOR_IMAGE}" \
		"${SKUPPER_ROUTER_IMAGE}" \
		"${SKUPPER_NETWORK_OBSERVER_IMAGE}"; do
		if ${CONTAINER} image inspect "$image" > /dev/null 2>&1; then
		    ${KIND} load docker-image --name="$1" "$image"
		else
			echo "(skdev) WARNING: skipped loading image $image"
		fi
	done
}

kind::imageload::ociarchive() {
		for archive in ./oci-archives/*.tar; do
			${KIND} load image-archive --name="$1" "$archive"
		done
}

kind::imageload::podman() {
	for image in "${SKUPPER_CONTROLLER_IMAGE}" \
		"${SKUPPER_KUBE_ADAPTOR_IMAGE}" \
		"${SKUPPER_ROUTER_IMAGE}" \
		"${SKUPPER_NETWORK_OBSERVER_IMAGE}"; do
		if podman image inspect "$image" > /dev/null 2>&1; then
			${KIND} load image-archive --name="$1" <(podman image save "$image")
		else
			echo "(skdev) WARNING: skipped loading image $image"
		fi
	done
}

container::network::subnet() {
	    subnetPath='.[].IPAM.Config[].Subnet  | select(test("([0-9]{1,3}[.]){3}[0-9]{1,3}/[1-3]?[0-9]")?)'
		if [ "${CONTAINER}" == "podman" ]; then
		    subnetPath='.[].subnets[].subnet | select(test("([0-9]{1,3}[.]){3}[0-9]{1,3}/[1-3]?[0-9]")?)'
		fi
		${CONTAINER} network inspect "$1" | jq -r "$subnetPath"								
}

metallb::l2::config() {
		subnet=$(${PYTHON} -c "from ipaddress import ip_network; print(list(ip_network('$1').subnets(new_prefix=28))[-$2])")
		cat << EOF
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default
  namespace: metallb-system
spec:
  avoidBuggyIPs: true
  autoAssign: true
  addresses:
  - ${subnet}
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: default
  namespace: metallb-system
EOF
}

skupper::cluster::controller() {
	SKUPPER_TESTING=true scripts/skupper-deployment-generator.sh cluster ${IMAGE_TAG} ${ROUTER_IMAGE_TAG} false
}

main () {
	while [[ $# -gt 0 ]]; do
		case $1 in
			-h|--help)
				usage
				exit;
				;;
			-r|--recreate)
				FORCE_RECREATE="true"
				shift;;
			--metallb)
				METALLB="true"
				if [[ "${2-}" =~ ^[0-9]+$ ]]; then
					SUBNET="$2"
					shift
				fi
				shift;;
			-n|--name)
				CLUSTER="$2"
				shift
				shift
				;;
			-c|--container)
				CONTAINER="$2"
				shift
				shift
				;;				
			-i|--images)
				IMAGE_SOURCE="$2"
				shift
				shift
				;;
			-v|--verbose)
				VERBOSE="true"
				set -x
				KIND_LOG_LEVEL="6"
				shift
				;;				
			*)
				echo "Unknown argument $1"
				usage
				exit 1
				;;
		esac
	done

	ensure::kind
	ensure::container
	if [ -z "${KUBECONFIG-}" ]; then
		export KUBECONFIG="$HOME/.kube/skupperdev-config-$CLUSTER"
		echo "(skdev) WARNING: KUBECONFIG not set. Defaulting to ${KUBECONFIG}"
	fi

	exists=$(kind::cluster::list | grep "^${CLUSTER}\$") || true
	if [ "${FORCE_RECREATE}" == "true" ] && [ "$exists" ]; then
		echo "(skdev) deleting kind cluster ${CLUSTER}"
		kind::cluster::delete "${CLUSTER}"
		exists=""
	fi
	if [ -z "$exists" ]; then
		echo "(skdev) creating kind cluster ${CLUSTER}"
		kind::cluster::create "${CLUSTER}"
	fi
	case "$IMAGE_SOURCE" in
		none)
			;;
		docker)
			echo "(skdev) loading dev images from host docker image storage"
			kind::imageload::docker "${CLUSTER}"
			;;
		podman)
			echo "(skdev) loading dev images from host podman image storage"
			kind::imageload::podman "${CLUSTER}"
			;;
		ociarchive)
			echo "(skdev) loading dev images from ./oci-archives"
			kind::imageload::ociarchive "${CLUSTER}"
			;;
		*)
			echo "(skdev) WARNING: Unknown image option ${IMAGE_SOURCE}. Images will not loaded!"
			;;
	esac

	if [ "${METALLB}" == "true" ]; then
		ensure::helm
		ensure::python
		echo "(skdev) deploying metallb to ${CLUSTER}"
		kind_subnet=$(container::network::subnet kind)

		METALLB_DEBUG_FLAG=""
		if [[ "${VERBOSE}" == "true" ]]; then
			METALLB_DEBUG_FLAG="--debug"
		fi
		"${HELM}" repo add metallb https://metallb.github.io/metallb
		"${HELM}" upgrade --install metallb metallb/metallb \
			--namespace metallb-system --create-namespace \
			--set speaker.ignoreExcludeLB=true \
			--version 0.15.* \
			--wait ${METALLB_DEBUG_FLAG} # empty or --debug
		"${KUBECTL}" apply -f <(metallb::l2::config "$kind_subnet" "$SUBNET")
	fi

	echo "(skdev) configuring controller deployment"
	skupper::cluster::controller | "${KUBECTL}" apply -f -
}
main "$@"
