#!/usr/bin/env bash
# Copyright (c) HashiCorp, Inc.
# SPDX-License-Identifier: BUSL-1.1


readonly SCRIPT_NAME="$(basename ${BASH_SOURCE[0]})"
readonly SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
readonly SOURCE_DIR="$(dirname "$(dirname "${SCRIPT_DIR}")")"
readonly FN_DIR="$(dirname "${SCRIPT_DIR}")/functions"

source "${SCRIPT_DIR}/functions.sh"

unset CDPATH

set -euo pipefail

usage() {
cat <<-EOF
Usage: ${SCRIPT_NAME} [<options ...>]

Description:
    Installs various supporting Go tools.

Options:
    -protobuf                Just install tools for protobuf.
    -lint                    Just install tools for linting.
    -codegen                 Just install tools for codegen.
    -pre-commit              Just install pre-commit.
    -h | --help              Print this help text.
EOF
}

function err_usage {
    err "$1"
    err ""
    err "$(usage)"
}

function main {
    while test $# -gt 0
    do
        case "$1" in
            -protobuf )
                proto_tools_install
                return 0
                ;;
            -lint )
                lint_install
                return 0
                ;;
            -codegen )
                codegen_install
                return 0
                ;;
            -pre-commit )
                pre_commit_install
                return 0
                ;;
            -h | --help )
                usage
                return 0
                ;;
        esac
    done

    # ensure these tools are installed
    tools_install
}

function proto_tools_install {
    local protoc_gen_go_version
    local protoc_gen_go_grpc_version
    local buf_version
    local mog_version
    local protoc_go_inject_tag_version
    local mockery_version

    mockery_version="$(make --no-print-directory print-MOCKERY_VERSION)"
    protoc_gen_go_version="$(grep google.golang.org/protobuf go.mod | awk '{print $2}')"
    protoc_gen_go_grpc_version="$(make --no-print-directory print-PROTOC_GEN_GO_GRPC_VERSION)"
    mog_version="$(make --no-print-directory print-MOG_VERSION)"
    protoc_go_inject_tag_version="$(make --no-print-directory print-PROTOC_GO_INJECT_TAG_VERSION)"
    buf_version="$(make --no-print-directory print-BUF_VERSION)"
    protoc_gen_go_binary_version="$(make --no-print-directory print-PROTOC_GEN_GO_BINARY_VERSION)"

    # echo "go: ${protoc_gen_go_version}"
    # echo "mog: ${mog_version}"
    # echo "tag: ${protoc_go_inject_tag_version}"

    install_versioned_tool \
        'mockery' \
        'github.com/vektra/mockery/v3' \
        "${mockery_version}" \
        'github.com/vektra/mockery/v3'

    install_versioned_tool \
       'buf' \
        'github.com/bufbuild/buf' \
        "${buf_version}" \
        'github.com/bufbuild/buf/cmd/buf'

    install_versioned_tool \
        'protoc-gen-go' \
        'google.golang.org/protobuf' \
        "${protoc_gen_go_version}" \
        'google.golang.org/protobuf/cmd/protoc-gen-go'

    install_versioned_tool \
        'protoc-gen-go-grpc' \
        'google.golang.org/grpc/cmd/protoc-gen-go-grpc' \
        "${protoc_gen_go_grpc_version}" \
        'google.golang.org/grpc/cmd/protoc-gen-go-grpc'

    install_versioned_tool \
        protoc-gen-go-binary \
        'github.com/hashicorp/protoc-gen-go-binary' \
        "${protoc_gen_go_binary_version}" \
        'github.com/hashicorp/protoc-gen-go-binary'

    install_versioned_tool \
        'protoc-go-inject-tag' \
        'github.com/favadi/protoc-go-inject-tag' \
        "${protoc_go_inject_tag_version}" \
        'github.com/favadi/protoc-go-inject-tag'

    install_versioned_tool \
        'mog' \
        'github.com/hashicorp/mog' \
        "${mog_version}" \
        'github.com/hashicorp/mog'

    install_local_protoc_generator "${SOURCE_DIR}/internal/tools/protoc-gen-consul-rate-limit"
    
    install_local_protoc_generator "${SOURCE_DIR}/internal/resource/protoc-gen-resource-types"
    
    install_local_protoc_generator "${SOURCE_DIR}/internal/tools/protoc-gen-grpc-clone"

    install_local_protoc_generator "${SOURCE_DIR}/internal/resource/protoc-gen-json-shim"

    install_local_protoc_generator "${SOURCE_DIR}/internal/resource/protoc-gen-deepcopy"

    return 0
}

function lint_install {
    local lint_consul_retry_version
    lint_consul_retry_version="$(make --no-print-directory print-LINT_CONSUL_RETRY_VERSION)"
    
    local golangci_lint_version
    golangci_lint_version="$(make --no-print-directory print-GOLANGCI_LINT_VERSION)"

    local gci_version
    gci_version="$(make --no-print-directory print-GCI_VERSION)"

    install_versioned_tool \
        'lint-consul-retry' \
        'github.com/hashicorp/lint-consul-retry' \
        "${lint_consul_retry_version}" \
        'github.com/hashicorp/lint-consul-retry'

    install_unversioned_tool \
        'enumcover' \
        'github.com/reillywatson/enumcover/cmd/enumcover@master'

    install_versioned_tool \
        'golangci-lint' \
        'github.com/golangci/golangci-lint/v2' \
        "${golangci_lint_version}" \
        'github.com/golangci/golangci-lint/v2/cmd/golangci-lint'

    install_versioned_tool \
        'gci' \
        'github.com/daixiang0/gci' \
        "${gci_version}" \
        'github.com/daixiang0/gci'
}

function codegen_install {
  deepcopy_install
  copywrite_install
}

function deepcopy_install {
  local deep_copy_version
      deep_copy_version="$(make --no-print-directory print-DEEP_COPY_VERSION)"

      install_versioned_tool \
          'deep-copy' \
          'github.com/globusdigital/deep-copy' \
          "${deep_copy_version}" \
          'github.com/globusdigital/deep-copy' \
          'true'
}

function copywrite_install {
    local copywrite_version
    copywrite_version="$(make --no-print-directory print-COPYWRITE_TOOL_VERSION)"

    install_versioned_tool \
        'copywrite' \
        'github.com/hashicorp/copywrite' \
        "${copywrite_version}" \
        'github.com/hashicorp/copywrite'
}

function pre_commit_install {
    # if already installed make sure the hook is also installed
    if command -v "pre-commit" &>/dev/null; then
        # Not to be confused with installing the tool, this installs
        # the git hook locally (.git/hooks/pre-commit) which pre-commit
        # uses as a vector to run checks on `git commit`. This hook is
        # generated based on the local environment hence not source
        # controlled.
        pre-commit install
        return 0
    fi

    # Install options based on https://pre-commit.com/#installation
    if command -v "brew" &>/dev/null; then
        brew install pre-commit && pre-commit install
        return 0
    fi

    # Try python regardless of platform (mac, linux, etc)
    if command -v "pip3" &>/dev/null; then
        pip3 install pre-commit && pre-commit install
        return 0
    fi

    # Can't disappoint the linux/debian folks
    if command -v "apt" &>/dev/null; then
        sudo apt-get install -yq pre-commit && pre-commit install
        return 0
    fi

    if [[ "$(uname)" == "Darwin" ]]; then
        echo "ERROR: Install homebrew from https://brew.sh/ so that pre-commit (https://pre-commit.com) can be installed."
        return 1
    fi

    echo "ERROR: Install python3 and pip3 so that pre-commit (https://pre-commit.com) can be installed."
    return 1
}

function tools_install {
    lint_install
    proto_tools_install
    codegen_install
    pre_commit_install
    copywrite_install

    return 0
}

function install_unversioned_tool {
    local command="$1"
    local install="$2"

    if ! command -v "${command}" &>/dev/null ; then
        echo "installing tool: ${install}"
        go install "${install}"
    else
        echo "skipping tool: ${install} (installed)"
    fi

    return 0
}

function install_versioned_tool {
    local command="$1"
    local module="$2"
    local version="$3"
    local installbase="$4"
    local usego123="${5:-}"

    local should_install=
    local install_reason=
    local got
    local vgot
    local vneed

    local expect="${module}@${version}"
    local install="${installbase}@${version}"

    if [[ -z "$version" ]]; then
        err "cannot install '${command}' no version selected"
        return 1
    fi

    if [[ "$version" = "@DEV" ]]; then
        if ! command -v "${command}" &>/dev/null ; then
            err "dev version of '${command}' requested but not installed"
            return 1
        fi
        echo "skipping tool: ${installbase} (using development version)"
        return 0
    fi

    if command -v "${command}" &>/dev/null ; then
        mod_line="$(go version -m "$(which "${command}")" | grep '\smod\s')"
        act_mod=$(echo "${mod_line}" | awk '{print $2}')
        if [[ "$module" != "$act_mod" ]]; then
            err "${command} is already installed by module \"${act_mod}\" but should be installed by module \"${module}\". Delete it and re-run to re-install."
            return 1
        fi

        got="$(echo "${mod_line}" | grep "${module}" | awk '{print $2 "@" $3}')"
        if [[ "$expect" != "$got" ]]; then
            should_install=1
            install_reason="upgrade"
        fi

        # check that they were compiled with the current version of go
        set +o pipefail
        vgot="$(go version -m $(which "${command}") | head -n 1 | grep -o 'go[0-9.]\+')"
        vneed="$(go version | head -n 1 | awk '{print $3}')"
        set -o pipefail
        if [[ "$vgot" != "$vneed" ]]; then
            should_install=1
            install_reason="go toolchain upgrade"
        fi
    else
        should_install=1
        install_reason="install"
    fi

    if [[ -n $should_install ]]; then
        # deep-copy does not compile with go1.25. https://github.com/globusdigital/deep-copy/issues/45
        # To work around this we install it with go1.23.
        # This can be removed when deep-copy supports go1.25.
        if [[ -n $usego123 ]]; then
          echo "installing tool (${install_reason}): ${install} with go 1.23"
          go install golang.org/dl/go1.23.12@latest
          go1.23.12 download
          go1.23.12 install "${install}"
        else
          echo "installing tool (${install_reason}): ${install}"
          go install "${install}"
        fi
    else
        echo "skipping tool: ${install} (installed)"
    fi
    return 0
}

function install_local_protoc_generator {
    local src=$1
    echo "installing tool $(basename $src) from local source"
    pushd -- "$src" > /dev/null
    go install
    popd > /dev/null
}

main "$@"
exit $?
