#fix ZSH -  perform field splitting - http://zsh.sourceforge.net/Doc/Release/Options.html
if [ -n "$ZSH_VERSION" ]; then
    setopt SH_WORD_SPLIT
fi

# helper functions
if [ -z "$FUBECTL_MSYS_HACK" ]; then
    alias _kctl_tty="kubectl"
    alias _inline_fzf="fzf --multi --ansi -i -1 --height=50% --reverse -0 --header-lines=1 --inline-info --border"
    alias _inline_fzf_nh="fzf --multi --ansi -i -1 --height=50% --reverse -0 --inline-info --border"
else # hack for Msys2 shell, where fzf doesn't support mintty
    alias _kctl_tty="winpty kubectl"
    alias _inline_fzf="fzf --multi --ansi -i -1 --reverse -0 --header-lines=1 --inline-info"
    alias _inline_fzf_nh="fzf --multi --ansi -i -1 --reverse -0 --inline-info"
fi

function _isClusterSpaceObject() {
  # caller is responsible for assuring non-empty "$1"
    obj="$1"
    kubectl api-resources --namespaced=false \
      | awk '(apiidx){print substr($0, 0, apiidx),substr($0, kindidx) } (!apiidx){ apiidx=index($0, " APIVERSION");kindidx=index($0, " KIND")}' \
      | grep -iq "\<${obj}\>"
}

# [k] like g for git but 233% as effective!
alias k="kubectl"

# [kw] watch resources of any KIND in current namespace, usage: kw KIND[,KIND2,...]
alias kw="${FUBECTL_WATCH_CMD:-watch} kubectl get"

# [ka] get all pods from current namespace
alias ka="kubectl get pods"

# [kall] get all pods from cluster
alias kall="kubectl get pods --all-namespaces"

# [kwa] watch all pods in current namespace
alias kwa="${FUBECTL_WATCH_CMD:-watch} kubectl get pods"

# [kwall] watch all pods in cluster
alias kwall="${FUBECTL_WATCH_CMD:-watch} kubectl get pods --all-namespaces"

# TODO use "open" instead of "xdg-open" on a mac - also redirect xdg-open std{out,err} to /dev/null
# [kp] open kubernetes dashboard with proxy
alias kp="xdg-open 'http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/' & kubectl proxy"

# [kwatchn] watch a resource of KIND in current namespace, usage: kwatchn [KIND] - if KIND is empty then pod is used
function kwatchn() {
    local kind="${1:-pod}"
    kubectl get "${kind}" | _inline_fzf | awk '{print $1}' | xargs -r ${FUBECTL_WATCH_CMD:-watch} kubectl get "${kind}"
}

# [kwatch] watch a resource of KIND in cluster, usage: kwatch [KIND] - if KIND is empty then pod is used
function kwatch() {
    local kind="${1:-pod}"
    if _isClusterSpaceObject "$kind" ; then
        kubectl get "${kind}" | _inline_fzf | awk '{print $1}' | xargs -r ${FUBECTL_WATCH_CMD:-watch} kubectl get "${kind}"
    else
        kubectl get "${kind}" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r ${FUBECTL_WATCH_CMD:-watch} kubectl get "${kind}" -n
    fi
}

# [kcmd] create a pod from IMAGE (ubuntu by default) and execute CMD (bash by default), usage: kcmd [CMD] [IMAGE]
function kcmd() {
    local cmd="$1"
    local image="${2:-ubuntu}"
    local ns="$(kubectl get ns | _inline_fzf | awk '{print $1}')"
    if [ -n "$cmd" ]; then
        kubectl run shell-$RANDOM --namespace $ns --rm -i --tty --image ${image} -- /bin/sh -c "${cmd}"
    else
        kubectl run shell-$RANDOM --namespace $ns --rm -i --tty --image ${image} -- /bin/bash
    fi
}

# [kube_ctx_name] get the current context
function kube_ctx_name() {
    kubectl config current-context
}

# [kube_ctx_namespace] get current namespace
function kube_ctx_namespace() {
    local default_ns="$(kubectl config view --minify|grep namespace: |sed 's/namespace: //g'|tr -d ' ')"
    default_ns="${default_ns:-default}"
    echo "$default_ns"
}

# [kgetn] get resource of KIND from current namespace, usage: kgetn [KIND] - if KIND is empty then pod is used
function kgetn() {
    local kind="${1:-pod}"
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl get -o yaml "$kind"
}

# [kget] get resource of KIND from cluster, usage: kget [KIND] - if KIND is empty then pod is used
function kget() {
    local kind="${1:-pod}"
    if _isClusterSpaceObject "$kind" ; then
        kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl get -o yaml "$kind"
    else
        kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r kubectl get -o yaml "$kind" -n
    fi
}

# [kexp] as former `--export` field removes unwanted metadata - usage: COMMAND | kexp
function kexp() {
    if [ -t 0 ]; then
        echo "kexp has no piped input!"
        echo "usage: COMMAND | kexp"
    else
        # remove not neat fields
        kubectl neat
    fi
}

# [kget-exp] get a resource by its YAML as former `--export` flag
function kget-exp() {
    kget "$@" | kexp
}

# [kedn] edit resource of KIND from current namespace, usage: kedn [KIND] - if KIND is empty then pod is used
function kedn() {
    local kind="${1:-pod}"
    kubectl edit "${kind}" "$(kubectl get "${kind}" | _inline_fzf | awk '{print $1}')"
}

# [ked] edit resource of KIND from cluster, usage: ked [KIND] - if KIND is empty then pod is used
function ked() {
    local kind="${1:-pod}"
    local edit_args
    if _isClusterSpaceObject $kind ; then
        edit_args=( $(kubectl get "$kind" | _inline_fzf | awk '{print $1}') )
    else
        edit_args=( $(kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print "-n", $1, $2}') )
    fi
    kubectl edit "$kind" ${edit_args[*]}
}

# [kdesn] describe resource of KIND in current namespace, usage: kdesn [KIND] - if KIND is empty then pod is used
function kdesn() {
    local kind="${1:-pod}"
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl describe "$kind"
}

# [kdes] describe resource of KIND in cluster, usage: kdes [KIND] - if KIND is empty then pod is used
function kdes() {
    local kind="${1:-pod}"
    if _isClusterSpaceObject "$kind" ; then
        kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl describe "$kind"
    else
        kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r kubectl describe "$kind" -n
    fi
}

# [kdeln] delete resource of KIND in current namespace, usage: kdeln [KIND] - if KIND is empty then pod is used
function kdeln() {
    local kind="${1:-pod}"
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r -p kubectl delete "$kind"
}

# [kdel] delete resource of KIND in cluster, usage: kdel [KIND] - if KIND is empty then pod is used
function kdel() {
    local kind="${1:-pod}"
    if _isClusterSpaceObject "$kind" ; then
        kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r -p kubectl delete "$kind"
    else
        kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r -p kubectl delete "$kind" -n
    fi
}

function _klog_usage() {
  cat <<'EOF'
Usage: klog[n] [LINECOUNT] [options]

First argument is interpreted as LINECOUNT if it matches integer syntax.
Additional `options` are passed on (see `kubectl logs --help` for details).
EOF
}

# [klogn] fetch log from container in current namespace
function klogn() {
    [ "$1" = "--help" ] && _klog_usage && return
    local line_count=10
    if [[ $1 =~ ^[-]{0,1}[0-9]+$ ]]; then
        line_count="$1"
        shift
    fi

    local pod=$(kubectl get po | _inline_fzf | awk '{print $1}')
    [ -z "${pod}" ] && printf "klogn: no pods found. no logs can be shown.\n" && return
    local containers_out=$(kubectl get po "${pod}" -o=jsonpath='{.spec.containers[*].name} {.spec.initContainers[*].name}' | sed 's/ $//')
    local container_choosen=$(echo "$containers_out" |  tr ' ' "\n" | _inline_fzf_nh)
    _kctl_tty logs "${pod}" -c "${container_choosen}" --tail="${line_count}" "$@"
}

# [klog] fetch log from container in the cluster
function klog() {
    [ "$1" = "--help" ] && _klog_usage && return
    local line_count=10
    if [[ $1 =~ ^[-]{0,1}[0-9]+$ ]]; then
        line_count="$1"
        shift
    fi

    local arg_pair=$(kubectl get po --all-namespaces | _inline_fzf | awk '{print $1, $2}')
    [ -z "$arg_pair" ] && printf "klog: no pods found. no logs can be shown.\n" && return
    local containers_out=$(echo "$arg_pair" | xargs kubectl get po -o=jsonpath='{.spec.containers[*].name} {.spec.initContainers[*].name}' -n | sed 's/ $//')
    local container_choosen=$(echo "$containers_out" |  tr ' ' "\n" | _inline_fzf_nh)
    _kctl_tty logs -n ${arg_pair} -c "${container_choosen}" --tail="${line_count}" "$@"
}

# [kkonfig] select a file in current directory and set it as $KUBECONFIG
function kkonfig() {
    local kubeconfig
    kubeconfig=$(_inline_fzf_nh) || return
    export KUBECONFIG=$PWD/$kubeconfig
    exec $SHELL
}

# [kexn] execute command in container from current namespace, usage: kexn CMD [ARGS]
function kexn() {
    [ -z "$1" ] && printf "kexn: missing argument(s).\nUsage: kexn CMD [ARGS]\n" && return 255
    local arg_pair=$(kubectl get po | _inline_fzf | awk '{print $1}')
    [ -z "$arg_pair" ] && printf "kexn: no pods found. no execution.\n" && return
    local containers_out=$(echo "$arg_pair" | xargs kubectl get po -o=jsonpath='{.spec.containers[*].name}' -n)
    local container_choosen=$(echo "$containers_out" |  tr ' ' "\n" | _inline_fzf_nh)
    _kctl_tty exec -it ${arg_pair} -c "${container_choosen}" -- "$@"
}

# [kex] execute command in container from cluster, usage: kex CMD [ARGS]
function kex() {
    [ -z "$1" ] && printf "kex: missing argument(s).\nUsage: kex CMD [ARGS]\n" && return 255
    local arg_pair=$(kubectl get po --all-namespaces | _inline_fzf | awk '{print $1, $2}')
    [ -z "$arg_pair" ] && printf "kex: no pods found. no execution.\n" && return
    local containers_out=$(echo "$arg_pair" | xargs kubectl get po -o=jsonpath='{.spec.containers[*].name}' -n)
    local container_choosen=$(echo "$containers_out" |  tr ' ' "\n" | _inline_fzf_nh)
    _kctl_tty exec -it -n ${arg_pair} -c "${container_choosen}" -- "$@"
}

# [kforn] port-forward a container port from current namesapce, usage: kforn LOCAL_PORT:CONTAINER_PORT
function kforn() {
    local port="$1"
    [ -z "$port" ] && printf "kforn: missing argument.\nUsage: kforn LOCAL_PORT:CONTAINER_PORT\n" && return 255
    local arg_pair="$(kubectl get po | _inline_fzf | awk '{print $1}')"
    [ -z "$arg_pair" ] && printf "kforn: no pods found. no forwarding.\n" && return
    _kctl_tty port-forward ${arg_pair} ${port}
}

# [kfor] port-forward a container port from cluster, usage: kfor LOCAL_PORT:CONTAINER_PORT
function kfor() {
    local port="$1"
    [ -z "$port" ] && printf "kfor: missing argument.\nUsage: kfor LOCAL_PORT:CONTAINER_PORT\n" && return 255
    local arg_pair="$(kubectl get po --all-namespaces | _inline_fzf | awk '{print $1, $2}')"
    [ -z "$arg_pair" ] && printf "kfor: no pods found. no forwarding.\n" && return
    _kctl_tty port-forward -n ${arg_pair} ${port}
}

# [ksearch] search for string in resources
function ksearch() {
    local search_query="$1"
    [ -z "$search_query" ] && printf "ksearch: missing argument.\nUsage: ksearch SEARCH_QUERY\n" && return 255
    for ns in $(kubectl get --export -o=json ns | jq -r '.items[] | .metadata.name'); do
        kubectl --namespace="${ns}" get --export -o=json \
            deployment,ingress,daemonset,secrets,configmap,service,serviceaccount,statefulsets,pod,endpoints,customresourcedefinition,events,networkpolicies,persistentvolumeclaims,persistentvolumes,replicasets,replicationcontrollers,statefulsets,storageclasses | \
        jq '.items[]' -c | \
        grep "$search_query" | \
        jq -r  '. | [.kind, .metadata.name] | @tsv' | \
        awk -v prefix="$ns" '{print "kubectl get -n " prefix " " $0}'
    done
}

# [kcl] context list
alias kcl='kubectl config get-contexts'

# [kcs] context set
function kcs() {
    local context="$(kubectl config get-contexts | _inline_fzf | cut -b4- | awk '{print $1}')"
    kubectl config set current-context "${context}"
}

# [kcns] context set default namespace
function kcns() {
    local ns="$1"
    if [ -z "$ns" ]; then
        ns="$(kubectl get ns | _inline_fzf | awk '{print $1}')"
    fi
    [ -z "$ns" ] && printf "kcns: no namespace selected/found.\nUsage: kcns [NAMESPACE]\n" && return
    kubectl config set-context "$(kubectl config current-context)" --namespace="${ns}"
}

# [kwns] watch pods in a namespace
function kwns() {
    local ns=$(kubectl get ns | _inline_fzf | awk '{print $1}')
    [ -z "$ns" ] && printf "kcns: no namespace selected/found.\nUsage: kwns\n" && return
    ${FUBECTL_WATCH_CMD:-watch} kubectl get pod -n "$ns"
}

# [ktreen] prints a tree of k8s objects from current namespace, usage: ktreen [KIND]
function ktreen() {
    local kind="$1"
    if [ -z "$kind" ]; then
        local kind="$(kubectl api-resources -o name | _inline_fzf | awk '{print $1}')"
    fi
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl tree "$kind"
}

# [ktree] prints a tree of k8s objects from cluster, usage: ktree [KIND]
function ktree() {
    local kind="$1"
    if [ -z "$kind" ]; then
        local kind="$(kubectl api-resources -o name | _inline_fzf | awk '{print $1}')"
    fi
    if _isClusterSpaceObject "$kind" ; then
        kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl tree "$kind"
    else
        kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r kubectl tree "$kind" -n
    fi
}

# [konsole] create root shell on a node
function konsole() {
    local node_hostname="$(kubectl get node --label-columns=kubernetes.io/hostname | _inline_fzf | awk '{print $6}')"
    local ns="$(kubectl get ns | _inline_fzf | awk '{print $1}')"
    local name=shell-$RANDOM
    local overrides='
{
    "spec": {
        "hostPID": true,
        "hostNetwork": true,
        "tolerations": [
            {
                "operator": "Exists",
                "effect": "NoSchedule"
            },
            {
                "operator": "Exists",
                "effect": "NoExecute"
            }
        ],
        "containers": [
            {
                "name": "'$name'",
                "image": "alpine",
                "command": [
                    "/bin/sh"
                ],
                "args": [
                    "-c",
                    "nsenter -t 1 -m -u -i -n -p -- bash"
                ],
                "resources": null,
                "stdin": true,
                "stdinOnce": true,
                "terminationMessagePath": "/dev/termination-log",
                "terminationMessagePolicy": "File",
                "tty": true,
                "securityContext": {
                    "privileged": true
                }
            }
        ],
        "nodeSelector": {
            "kubernetes.io/hostname": "'$node_hostname'"
        }
    }
}
'
    kubectl run $name --namespace "$ns" --rm -it --image alpine --overrides="${overrides}"
}

# [ksecn] decode a value from a secret in current namespace, usage: ksecn
function ksecn() {
    local secret=$(kubectl get secret | _inline_fzf | awk '{print $1}')
    local key=$(kubectl get secret "${secret}" -o go-template='{{- range $k,$v := .data -}}{{- printf "%s\n" $k -}}{{- end -}}' | _inline_fzf_nh)
    kubectl get secret "${secret}" -o go-template='{{ index .data "'$key'" | base64decode }}'
}

# [ksec] decode a value from a secret in cluster, usage: ksec
function ksec() {
    local ns=$(kubectl get ns | _inline_fzf | awk '{print $1}')
    local secret=$(kubectl get secret -n "$ns" | _inline_fzf | awk '{print $1}')
    local key=$(kubectl get secret -n "$ns" "$secret" -o go-template='{{- range $k,$v := .data -}}{{- printf "%s\n" $k -}}{{- end -}}' | _inline_fzf_nh)
    kubectl get secret -n "$ns" "${secret}" -o go-template='{{ index .data "'$key'" | base64decode }}'
}

# [kinstall] Install the required kubectl plugins
function kinstall() {
    kubectl krew install tree
    kubectl krew install neat
}
# [kupdate] Updates kubectl plugins
function kupdate() {
    kubectl krew upgrade
}

# [krrs] restart resource of KIND, usage: krrs [KIND] - if KIND is empty then deployment is used
function krrs() {
    local kind="${1:-deploy}"
    kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r kubectl rollout restart "$kind" -n
}

# [krrsn] restart resource of KIND in current namespace, usage: krrsn [KIND] - if KIND is empty then deployment is used
function krrsn() {
    local kind="${1:-deploy}"
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl rollout restart "$kind"
}

# [krst] status of resource of KIND, usage: krst [KIND] - if KIND is empty then deployment is used
function krst() {
    local kind="${1:-deploy}"
    kubectl get "$kind" --all-namespaces | _inline_fzf | awk '{print $1, $2}' | xargs -r kubectl rollout status "$kind" -n
}

# [krstn] status resource of KIND in current namespace, usage: krstn [KIND] - if KIND is empty then deployment is used
function krstn() {
    local kind="${1:-deploy}"
    kubectl get "$kind" | _inline_fzf | awk '{print $1}' | xargs -r kubectl rollout status "$kind"
}

#### Kubermatic KKP specific
# [kkp-cluster] Kubermatic KKP - extracts kubeconfig of user cluster and connects it in a new bash
function kkp-cluster() {
    TMP_KUBECONFIG=$(mktemp)
    local cluster="$(kubectl get cluster | _inline_fzf | awk '{print $1}')"
    kubectl get secret admin-kubeconfig -n cluster-$cluster -o go-template='{{ index .data "kubeconfig" | base64decode }}' > $TMP_KUBECONFIG
    KUBECONFIG=$TMP_KUBECONFIG $SHELL
}

# [khelp] show this help message
function khelp() {
    echo "Usage of fubectl"
    echo
    echo "Reduces repetitive interactions with kubectl"
    echo "Find more information at https://github.com/kubermatic/fubectl"
    echo
    echo "Usage:"
    if [ -n "$ZSH_VERSION" ]
    then
        grep -E '^# \[.+\]' "${(%):-%x}"
    else
        grep -E '^# \[.+\]' "${BASH_SOURCE[0]}"
    fi
}
