#!/bin/bash -e

SERVICES_METHODS="backup configure deploy diagnose info list play resources restore start status stop undeploy"
SERVICES_PARAMS_METHODS="backup configure deploy restore undeploy"

SERVICES_LIST_CACHE_TTL=60
SERVICES_LIST_CACHE_FILE="/tmp/alteratorctl/services-list.cache"

services_list_cache_clear() {
    rm -f "$SERVICES_LIST_CACHE_FILE" 2>/dev/null || true
}

services_list_cache_read() {
    if [[ ! -s "$SERVICES_LIST_CACHE_FILE" ]]; then
        return 1
    fi

    local now_ts cache_ts
    now_ts="$(date +%s)"
    cache_ts="$(stat -c %Y "$SERVICES_LIST_CACHE_FILE" 2>/dev/null || echo 0)"

    if (( now_ts - cache_ts > SERVICES_LIST_CACHE_TTL )); then
        services_list_cache_clear
        return 1
    fi

    local cached
    cached="$(cat "$SERVICES_LIST_CACHE_FILE")"
    if [[ -z "$cached" ]] || ! echo "$cached" | grep -Eq '^service-[a-zA-Z0-9._-]+$'; then
        services_list_cache_clear
        return 1
    fi

    echo "$cached"
}

services_list_cache_write() {
    local parsed_list="$1"
    [[ -z "$parsed_list" ]] && return 1
    mkdir -p "$(dirname "$SERVICES_LIST_CACHE_FILE")" 2>/dev/null || true
    printf '%s\n' "$parsed_list" > "${SERVICES_LIST_CACHE_FILE}.tmp" 2>/dev/null || true
    mv -f "${SERVICES_LIST_CACHE_FILE}.tmp" "$SERVICES_LIST_CACHE_FILE" 2>/dev/null || true
}

get_services_list_output() {
    local list_output
    list_output="$(actl service list 2>/dev/null || true)"
    if ! echo "$list_output" | grep -Eq 'service-[a-zA-Z0-9._-]+'; then
        list_output="$(actl services list 2>/dev/null || true)"
    fi
    if ! echo "$list_output" | grep -Eq 'service-[a-zA-Z0-9._-]+'; then
        return 1
    fi

    echo "$list_output"
}

get_services_list() {
    local cached
    cached="$(services_list_cache_read)"
    if [[ -n "$cached" ]]; then
        echo "$cached"
        return 0
    fi

    local list_output
    list_output="$(get_services_list_output)"
    if [[ -z "$list_output" ]]; then
        return 1
    fi

    local parsed
    parsed="$(echo "$list_output" | awk '
        {
            line = $0
            if (line ~ /\(service-[^)]+\)/) {
                sub(/^.*\(/, "", line)
                sub(/\).*$/, "", line)
                print line
                next
            }

            for (i = 1; i <= NF; i++) {
                if ($i ~ /^service-[A-Za-z0-9._-]+$/) {
                    print $i
                    break
                }
            }
        }
    ' | sort -u)"

    if [[ -z "$parsed" ]]; then
        return 1
    fi

    services_list_cache_write "$parsed"
    echo "$parsed"
}

get_service_help_output() {
    local method="$1"
    local service="$2"

    local help_output
    help_output="$(actl services "$method" "$service" -h 2>/dev/null || true)"
    if [[ -z "$help_output" ]]; then
        help_output="$(actl services "$service" -h 2>/dev/null || true)"
    fi
    if [[ -z "$help_output" ]]; then
        return 1
    fi

    if ! echo "$help_output" | grep -q -- '--'; then
        return 1
    fi

    echo "$help_output"
}

get_param_enum_values() {
    local method="$1"
    local service="$2"
    local param_name="$3"
    local help_output

    help_output="$(get_service_help_output "$method" "$service")"
    if [[ -z "$help_output" ]]; then
        return 1
    fi

    echo "$help_output" | awk -v p="--${param_name}=" '
        {
            line = $0
            sub(/^[[:space:]]+/, "", line)
            if (index(line, p) != 1)
                next

            rest = substr(line, length(p) + 1)
            if (match(rest, /^<[^>]*\|[^>]*>/) == 0)
                next

            enum_spec = substr(rest, 2, RLENGTH - 2)
            n = split(enum_spec, values, "|")
            for (i = 1; i <= n; i++)
                if (length(values[i]) > 0)
                    print values[i]
            exit
        }
    '
}

get_service_params() {
    local method="$1"
    local service="$2"

    if [[ -z "$method" ]] || [[ -z "$service" ]]; then
        return 1
    fi

    if ! echo "$SERVICES_PARAMS_METHODS" | grep -qw "$method"; then
        return 1
    fi

    local help_output
    help_output="$(get_service_help_output "$method" "$service")"
    if [[ -z "$help_output" ]]; then
        return 1
    fi

    echo "$help_output" | sed -n 's/^[[:space:]]*\(--[^[:space:]]*\).*/\1/p' | \
        sed -E 's/<([0-9]+)-\.\.\./<\1.../g; s/=<[^>]*>/=/g' | \
        sort | uniq
}

services_build_options_from_context() {
    local wrapper_current_word="$1"
    local options=""

    if [[ "${COMP_WORDS[1]}" != "services" ]] || [[ "${COMP_CWORD}" -lt 2 ]]; then
        return 1
    fi

    local method_idx=2
    local service_idx=3
    local param_idx=4

    if [[ "${COMP_CWORD}" -eq 2 ]]; then
        echo "$SERVICES_METHODS"
        return 0
    fi

    if [[ "${COMP_CWORD}" -eq 3 ]]; then
        local method="${COMP_WORDS[$method_idx]}"
        if [[ "$method" == "list" ]]; then
            echo "--verbose --display-name-only --no-display-name --path-only --name-only --except-status-markers --no-name --hide-markers-legend -v -d -D -p -n -E -N -H"
        else
            get_services_list
        fi
        return 0
    fi

    if [[ "${COMP_CWORD}" -ge 4 ]]; then
        local method="${COMP_WORDS[$method_idx]}"
        local service="${COMP_WORDS[$service_idx]}"
        local current_word="${COMP_WORDS[${COMP_CWORD}]}"
        local prev_word=""
        local is_enum_value_completion=0
        local is_value_context=0

        if [[ -z "$current_word" ]]; then
            current_word="$wrapper_current_word"
        fi
        if [[ "${COMP_CWORD}" -gt 0 ]]; then
            prev_word="${COMP_WORDS[$((COMP_CWORD-1))]}"
        fi

        if [[ -n "$service" ]] && [[ ! "$service" =~ ^- ]]; then
            local params
            params="$(get_service_params "$method" "$service")"

            declare -A used_params_map
            declare -A selected_value_by_param
            declare -A params_set

            local selected_pairs=""
            local i arg_word used_param pair_key pair_value
            for ((i=param_idx; i<COMP_CWORD; i++)); do
                arg_word="${COMP_WORDS[$i]}"
                [[ "$arg_word" != --* ]] && continue
                used_param="${arg_word%%=*}"
                used_params_map["$used_param"]=1

                if [[ "$arg_word" == *=* ]]; then
                    pair_key="${arg_word%%=*}"
                    pair_value="${arg_word#*=}"
                    if [[ -n "$pair_value" ]]; then
                        selected_pairs="${selected_pairs}${pair_key}=${pair_value}"$'\n'
                        selected_value_by_param["$pair_key"]="$pair_value"
                    fi
                fi
            done

            local p
            while IFS= read -r p; do
                [[ -z "$p" ]] && continue
                params_set["${p%%=*}"]=1
            done <<< "$params"

            local filtered_params=""
            local param=
            local param_base=
            local control_parent=
            local control_child=
            local lookup_base=
            local rest_path=
            local selected_parent_value=
            while IFS= read -r param; do
                [[ -z "$param" ]] && continue
                param_base="${param%%=*}"

                if [[ -n "${used_params_map[$param_base]}" ]]; then
                    continue
                fi

                control_parent=""
                control_child=""
                lookup_base="$param_base"
                while [[ "$lookup_base" == *.* ]]; do
                    lookup_base="${lookup_base%.*}"
                    if [[ -n "${params_set[$lookup_base]}" ]]; then
                        control_parent="$lookup_base"
                        rest_path="${param_base#"${control_parent}".}"
                        control_child="${rest_path%%.*}"
                        break
                    fi
                done

                if [[ -n "$control_parent" && -n "$control_child" ]]; then
                    selected_parent_value="${selected_value_by_param[$control_parent]}"
                    if [[ -z "$selected_parent_value" || "$selected_parent_value" != "$control_child" ]]; then
                        continue
                    fi
                fi

                filtered_params="${filtered_params}${param}"$'\n'
            done <<< "$params"
            params="${filtered_params%$'\n'}"

            local enum_value_mode=""
            if [[ "$current_word" == "--"*"="* ]]; then
                local current_param="${current_word%%=*}"
                local current_value_prefix="${current_word#*=}"
                enum_value_mode="inline"
                is_value_context=1
            elif [[ "$prev_word" == "--"*"=" ]] && [[ "$current_word" != -* ]]; then
                local current_param="${prev_word%%=*}"
                local current_value_prefix="$current_word"
                enum_value_mode="split"
                is_value_context=1
            fi

            if [[ -n "$enum_value_mode" ]]; then
                local enum_values
                enum_values="$(get_param_enum_values "$method" "$service" "${current_param#--}")"
                if [[ -n "$enum_values" ]]; then
                    options=""
                    local enum_value
                    while IFS= read -r enum_value; do
                        [[ -z "$enum_value" ]] && continue
                        if [[ -z "$current_value_prefix" || "$enum_value" == "$current_value_prefix"* ]]; then
                            options="${options}${enum_value}"$'\n'
                        fi
                    done <<< "$enum_values"
                    options="${options%$'\n'}"
                    is_enum_value_completion=1
                fi
            fi

            if [[ "$is_enum_value_completion" -eq 1 ]]; then
                true
            elif [[ "$is_value_context" -eq 1 ]]; then
                options=""
            else
                options="$params"
            fi
        fi

        if [[ "$is_enum_value_completion" -eq 0 && "$is_value_context" -eq 0 ]]; then
            options="${options} -h --help --yes -y"
        fi

        echo "$options"
        return 0
    fi

    return 1
}
