#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_DOCKER_ROOT="${HOME}/projects/caddy"
DEFAULT_BINARY_ROOT="/etc/caddy"

usage() {
	cat <<'EOF'
Usage:
  configure_caddy_shell.sh [options]

Options:
  --mode <binary|docker>
  --docker-root <path>
  --helper <path>
  --helper-env <path>
  --shell <name>
  --rc-file <path>
  --help
EOF
}

stderr() {
	printf '%s\n' "$*" >&2
}

fail() {
	stderr "$@"
	exit 1
}

read_helper_value() {
	local helper_env="$1"
	local key="$2"
	[[ -f "${helper_env}" ]] || return 1
	sed -n "s/^${key}=\\\"\\(.*\\)\\\"$/\\1/p" "${helper_env}" | head -n 1
}

detect_shell_name() {
	local requested_shell="${1:-}"

	if [[ -n "${requested_shell}" ]]; then
		basename "${requested_shell}"
		return 0
	fi

	if [[ -n "${SHELL:-}" ]]; then
		basename "${SHELL}"
		return 0
	fi

	printf 'bash\n'
}

default_rc_file() {
	local shell_name="$1"

	case "${shell_name}" in
	zsh)
		printf '%s\n' "${HOME}/.zshrc"
		;;
	fish)
		printf '%s\n' "${HOME}/.config/fish/config.fish"
		;;
	bash)
		printf '%s\n' "${HOME}/.bashrc"
		;;
	*)
		printf '%s\n' "${HOME}/.profile"
		;;
	esac
}

looks_like_docker_root() {
	local candidate="$1"

	[[ -f "${candidate}/.caddy_helper.sh" || -f "${candidate}/.bash_caddy" ]] || return 1
	[[ -f "${candidate}/compose.yml" || -f "${candidate}/compose.yaml" ]] || return 1
	[[ -f "${candidate}/Caddyfile" || -f "${candidate}/.helper.env" || -f "${candidate}/conf/Caddyfile" ]]
}

find_docker_root_in_tree() {
	local search_dir="${PWD}"

	while [[ -n "${search_dir}" && "${search_dir}" != "/" ]]; do
		if looks_like_docker_root "${search_dir}"; then
			printf '%s\n' "${search_dir}"
			return 0
		fi
		search_dir="$(dirname "${search_dir}")"
	done

	if looks_like_docker_root "/"; then
		printf '%s\n' "/"
		return 0
	fi

	return 1
}

detect_mode() {
	local requested_mode="$1"
	local docker_root="$2"

	if [[ -n "${requested_mode}" ]]; then
		printf '%s\n' "${requested_mode}"
		return 0
	fi

	if looks_like_docker_root "${SCRIPT_DIR}" || find_docker_root_in_tree >/dev/null || [[ -n "${docker_root}" ]]; then
		printf 'docker\n'
		return 0
	fi

	printf 'binary\n'
}

default_docker_root() {
	if [[ -n "${CADDY_DOCKER_ROOT:-}" ]]; then
		printf '%s\n' "${CADDY_DOCKER_ROOT}"
		return 0
	fi

	if looks_like_docker_root "${SCRIPT_DIR}"; then
		printf '%s\n' "${SCRIPT_DIR}"
		return 0
	fi

	local detected_docker_root
	detected_docker_root="$(find_docker_root_in_tree)" || true
	if [[ -n "${detected_docker_root}" ]]; then
		printf '%s\n' "${detected_docker_root}"
		return 0
	fi

	printf '%s\n' "${DEFAULT_DOCKER_ROOT}"
}

default_helper_path() {
	local mode="$1"
	local docker_root="$2"

	case "${mode}" in
	binary)
		printf '%s/.caddy_helper.sh\n' "${DEFAULT_BINARY_ROOT}"
		;;
	docker)
		if [[ -f "${docker_root}/.caddy_helper.sh" ]]; then
			printf '%s/.caddy_helper.sh\n' "${docker_root}"
			return 0
		fi
		printf '%s/.bash_caddy\n' "${docker_root}"
		;;
	*)
		fail "Unsupported mode: ${mode}"
		;;
	esac
}

default_helper_env_path() {
	local mode="$1"
	local docker_root="$2"

	case "${mode}" in
	binary)
		printf '%s/.helper.env\n' "${DEFAULT_BINARY_ROOT}"
		;;
	docker)
		printf '%s/.helper.env\n' "${docker_root}"
		;;
	*)
		fail "Unsupported mode: ${mode}"
		;;
	esac
}

sync_docker_helper_env() {
	local helper_env_path="$1"
	local docker_root="$2"
	local editor_name="${CADDY_EDITOR:-}"
	local temp_file

	if [[ -z "${editor_name}" ]]; then
		editor_name="$(read_helper_value "${helper_env_path}" "CADDY_EDITOR")" || true
	fi

	if [[ -z "${editor_name}" ]]; then
		editor_name="vim"
	fi

	install -d -m 0755 "$(dirname "${helper_env_path}")"
	temp_file="$(mktemp)"
	cat >"${temp_file}" <<EOF
CADDY_MODE="docker"
CADDY_DOCKER_ROOT="${docker_root}"
CADDY_EDITOR="${editor_name}"
EOF
	install -m 0644 "${temp_file}" "${helper_env_path}"
	rm -f "${temp_file}"
}

render_posix_shell_hook() {
	local helper_path="$1"
	local helper_env_path="$2"
	local marker_begin="$3"
	local marker_end="$4"

	cat <<EOF
${marker_begin}
export CADDY_HELPER_ENV="${helper_env_path}"
if [ -f "${helper_path}" ]; then
    . "${helper_path}"
fi
${marker_end}
EOF
}

render_fish_shell_hook() {
	local helper_path="$1"
	local helper_env_path="$2"
	local marker_begin="$3"
	local marker_end="$4"

	cat <<EOF
${marker_begin}
set -gx CADDY_HELPER_ENV "${helper_env_path}"
function caddy
    bash -c 'source "\$1"; shift; caddy "\$@"' caddy "${helper_path}" \$argv
end
${marker_end}
EOF
}

replace_marked_block() {
	[[ $# -eq 4 ]] || fail "Internal error: replace_marked_block expects 4 arguments, got $#"

	local rc_file="$1"
	local marker_begin="$2"
	local marker_end="$3"
	local rendered_hook="$4"
	local temp_file

	install -d -m 0755 "$(dirname "${rc_file}")"
	touch "${rc_file}"

	if grep -Fq "${marker_begin}" "${rc_file}" 2>/dev/null; then
		temp_file="$(mktemp)"
		awk -v begin="${marker_begin}" -v end="${marker_end}" -v hook="${rendered_hook}" '
            BEGIN {
                in_block = 0
                replaced = 0
            }
            $0 == begin {
                if (!replaced) {
                    print hook
                    replaced = 1
                }
                in_block = 1
                next
            }
            $0 == end {
                in_block = 0
                next
            }
            !in_block {
                print
            }
            END {
                if (!replaced) {
                    print hook
                }
            }
        ' "${rc_file}" >"${temp_file}"
		cat "${temp_file}" >"${rc_file}"
		rm -f "${temp_file}"
		return 0
	fi

	if [[ -s "${rc_file}" ]]; then
		printf '\n' >>"${rc_file}"
	fi
	printf '%s\n' "${rendered_hook}" >>"${rc_file}"
}

configure_shell_hook() {
	local shell_name="$1"
	local rc_file="$2"
	local helper_path="$3"
	local helper_env_path="$4"
	local marker_begin="# caddy-helper"
	local marker_end="# /caddy-helper"
	local rendered_hook

	case "${shell_name}" in
	fish)
		rendered_hook="$(render_fish_shell_hook "${helper_path}" "${helper_env_path}" "${marker_begin}" "${marker_end}")"
		;;
	*)
		rendered_hook="$(render_posix_shell_hook "${helper_path}" "${helper_env_path}" "${marker_begin}" "${marker_end}")"
		;;
	esac

	replace_marked_block "${rc_file}" "${marker_begin}" "${marker_end}" "${rendered_hook}"
}

main() {
	local mode=""
	local docker_root=""
	local helper_path=""
	local helper_env_path=""
	local requested_shell=""
	local rc_file=""
	local shell_name

	while [[ $# -gt 0 ]]; do
		case "$1" in
		--mode)
			mode="${2:-}"
			shift 2
			;;
		--docker-root)
			docker_root="${2:-}"
			shift 2
			;;
		--helper)
			helper_path="${2:-}"
			shift 2
			;;
		--helper-env)
			helper_env_path="${2:-}"
			shift 2
			;;
		--shell)
			requested_shell="${2:-}"
			shift 2
			;;
		--rc-file)
			rc_file="${2:-}"
			shift 2
			;;
		--help | -h)
			usage
			return 0
			;;
		*)
			fail "Unknown argument: $1"
			;;
		esac
	done

	mode="$(detect_mode "${mode}" "${docker_root}")"

	if [[ "${mode}" == "docker" ]]; then
		docker_root="${docker_root:-$(default_docker_root)}"
	fi

	helper_path="${helper_path:-$(default_helper_path "${mode}" "${docker_root}")}"
	helper_env_path="${helper_env_path:-$(default_helper_env_path "${mode}" "${docker_root}")}"
	shell_name="$(detect_shell_name "${requested_shell}")"
	rc_file="${rc_file:-$(default_rc_file "${shell_name}")}"

	if [[ "${mode}" == "docker" ]]; then
		sync_docker_helper_env "${helper_env_path}" "${docker_root}"
	fi

	configure_shell_hook "${shell_name}" "${rc_file}" "${helper_path}" "${helper_env_path}"

	cat <<EOF
Caddy shell command configured.

Mode:
  ${mode}

Shell:
  ${shell_name}

RC file:
  ${rc_file}

Helper:
  ${helper_path}

Helper env:
  ${helper_env_path}

Next:
  exec \$SHELL -l
EOF

	if [[ "${shell_name}" != "fish" ]]; then
		cat <<EOF
  # or: source ${helper_path}
EOF
	fi
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
	main "$@"
fi
