#!/usr/bin/env bash

__caddy_command_list() {
	printf '%s\n' up down restart reload status logs edit fmt validate version raw help
}

__caddy_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
}

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

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

	if [[ -f "/.helper.env" ]]; then
		printf '%s\n' "/.helper.env"
		return 0
	fi

	return 1
}

__caddy_looks_like_docker_root() {
	local candidate="$1"
	[[ -f "${candidate}/compose.yml" || -f "${candidate}/compose.yaml" ]] || return 1
	[[ -f "${candidate}/Caddyfile" || -f "${candidate}/.helper.env" || -f "${candidate}/conf/Caddyfile" ]]
}

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

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

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

	return 1
}

__caddy_helper_script_dir() {
	local source_path="${BASH_SOURCE[0]:-}"
	if [[ -n "${source_path}" && -f "${source_path}" ]]; then
		cd "$(dirname "${source_path}")" >/dev/null 2>&1 && pwd
		return 0
	fi

	return 1
}

__caddy_active_helper_env() {
	if [[ -n "${CADDY_HELPER_ENV:-}" && -f "${CADDY_HELPER_ENV}" ]]; then
		printf '%s\n' "${CADDY_HELPER_ENV}"
		return 0
	fi

	__caddy_find_helper_env_in_tree && return 0

	if [[ -f "$(__caddy_binary_helper_env)" ]]; then
		printf '%s\n' "$(__caddy_binary_helper_env)"
		return 0
	fi

	local default_docker_root
	default_docker_root="$HOME/projects/caddy"
	if [[ -f "${default_docker_root}/.helper.env" ]]; then
		printf '%s\n' "${default_docker_root}/.helper.env"
		return 0
	fi

	return 1
}

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

	local active_helper_env
	active_helper_env="$(__caddy_active_helper_env)" || true
	if [[ -n "${active_helper_env}" ]]; then
		__caddy_read_helper_value "${active_helper_env}" "CADDY_MODE" && return 0
	fi

	printf 'auto\n'
}

__caddy_binary_config() {
	printf '%s\n' "${CADDY_BINARY_CONFIG:-/etc/caddy/Caddyfile}"
}

__caddy_binary_exec() {
	printf '%s\n' "${CADDY_BINARY_EXEC:-/usr/local/bin/caddy}"
}

__caddy_binary_helper_env() {
	printf '%s\n' "${CADDY_BINARY_HELPER_ENV:-/etc/caddy/.helper.env}"
}

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

	__caddy_find_docker_root_in_tree && return 0

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

	local active_helper_env
	active_helper_env="$(__caddy_active_helper_env)" || true
	if [[ -n "${active_helper_env}" ]]; then
		local configured_docker_root
		configured_docker_root="$(__caddy_read_helper_value "${active_helper_env}" "CADDY_DOCKER_ROOT")"
		if [[ -n "${configured_docker_root}" && -d "${configured_docker_root}" ]]; then
			printf '%s\n' "${configured_docker_root}"
			return 0
		fi
	fi

	local default_docker_root="$HOME/projects/caddy"
	if [[ -d "${default_docker_root}" ]]; then
		printf '%s\n' "${default_docker_root}"
		return 0
	fi

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

__caddy_docker_helper_env() {
	printf '%s\n' "$(__caddy_docker_root)/.helper.env"
}

__caddy_docker_source_config() {
	printf '%s/Caddyfile\n' "$(__caddy_docker_root)"
}

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

	local active_helper_env
	active_helper_env="$(__caddy_active_helper_env)" || true

	if [[ -n "${active_helper_env}" ]]; then
		__caddy_read_helper_value "${active_helper_env}" "CADDY_EDITOR" && return 0
	fi

	printf 'vim\n'
}

__caddy_resolved_mode() {
	local mode
	mode="$(__caddy_detect_mode)"

	if [[ "${mode}" != "auto" ]]; then
		printf '%s\n' "${mode}"
		return
	fi

	if [[ -f "$(__caddy_docker_root)/compose.yml" ]]; then
		printf 'docker\n'
		return
	fi

	printf 'binary\n'
}

__caddy_as_root() {
	if [[ "${EUID}" -eq 0 ]]; then
		"$@"
		return
	fi
	sudo "$@"
}

__caddy_print_step() {
	printf '[caddy] %s\n' "$1"
}

__caddy_print_reload_complete() {
	printf '[caddy] Reload completed.\n'
}

__caddy_print_version_header() {
	local mode="$1"
	printf 'Mode: %s\n' "${mode}"
}

__caddy_print_key_value() {
	local key="$1"
	local value="$2"
	printf '%-18s %s\n' "${key}:" "${value}"
}

__caddy_path_status() {
	local target_path="$1"
	if [[ -e "${target_path}" ]]; then
		printf 'present'
		return
	fi
	printf 'missing'
}

__caddy_binary_static_site_status() {
	if [[ -d "/etc/caddy/site" ]]; then
		printf 'enabled (/etc/caddy/site)'
		return
	fi
	printf 'disabled'
}

__caddy_docker_static_site_status() {
	local docker_root
	docker_root="$(__caddy_docker_root)"
	if grep -Fq "caddy_site:/srv" "${docker_root}/compose.yml" 2>/dev/null; then
		printf 'enabled (caddy_site:/srv)\n'
		return
	fi
	printf 'disabled\n'
}

__caddy_docker_volume_status() {
	local volume_name="$1"
	if docker volume inspect "${volume_name}" >/dev/null 2>&1; then
		printf 'present'
		return
	fi
	printf 'missing'
}

__caddy_print_binary_version_details() {
	local helper_env
	helper_env="$(__caddy_binary_helper_env)"

	__caddy_print_key_value "Command" "caddy version"
	__caddy_print_key_value "Binary" "$(__caddy_binary_exec)"
	__caddy_print_key_value "Config root" "/etc/caddy"
	__caddy_print_key_value "Caddyfile" "$(__caddy_binary_config)"
	__caddy_print_key_value "Log dir" "/etc/caddy/logs"
	__caddy_print_key_value "State dir" "/etc/caddy/state"
	__caddy_print_key_value "Static site" "$(__caddy_binary_static_site_status)"
	__caddy_print_key_value "Helper script" "/etc/caddy/.caddy_helper.sh"
	__caddy_print_key_value "Helper env" "${helper_env}"
	__caddy_print_key_value "Shell hook rc" "${HOME}/.$(basename "${SHELL:-bash}")rc"
	__caddy_print_key_value "Systemd unit" "/etc/systemd/system/caddy.service"

	printf '\nGenerated paths:\n'
	__caddy_print_key_value "binary" "/usr/local/bin/caddy [$(__caddy_path_status "/usr/local/bin/caddy")]"
	__caddy_print_key_value "config root" "/etc/caddy [$(__caddy_path_status "/etc/caddy")]"
	__caddy_print_key_value "caddyfile" "/etc/caddy/Caddyfile [$(__caddy_path_status "/etc/caddy/Caddyfile")]"
	__caddy_print_key_value "helper" "/etc/caddy/.caddy_helper.sh [$(__caddy_path_status "/etc/caddy/.caddy_helper.sh")]"
	__caddy_print_key_value "helper env" "/etc/caddy/.helper.env [$(__caddy_path_status "/etc/caddy/.helper.env")]"
	__caddy_print_key_value "logs" "/etc/caddy/logs [$(__caddy_path_status "/etc/caddy/logs")]"
	__caddy_print_key_value "state" "/etc/caddy/state [$(__caddy_path_status "/etc/caddy/state")]"
	__caddy_print_key_value "static site" "/etc/caddy/site [$(__caddy_path_status "/etc/caddy/site")]"
	__caddy_print_key_value "service" "/etc/systemd/system/caddy.service [$(__caddy_path_status "/etc/systemd/system/caddy.service")]"
	printf '\n'
}

__caddy_print_docker_version_details() {
	local docker_root
	docker_root="$(__caddy_docker_root)"

	__caddy_print_key_value "Command" "caddy version"
	__caddy_print_key_value "Docker root" "${docker_root}"
	__caddy_print_key_value "Compose file" "${docker_root}/compose.yml"
	__caddy_print_key_value "Source Caddyfile" "${docker_root}/Caddyfile"
	__caddy_print_key_value "Runtime config" "./Caddyfile:/etc/caddy/Caddyfile:ro"
	__caddy_print_key_value "Data volume" "caddy_data:/data"
	__caddy_print_key_value "Config volume" "caddy_config:/config"
	__caddy_print_key_value "Log volume" "caddy_logs:/var/log/caddy"
	__caddy_print_key_value "Static site" "$(__caddy_docker_static_site_status)"
	__caddy_print_key_value "Helper script" "${docker_root}/.caddy_helper.sh"
	__caddy_print_key_value "Helper env" "${docker_root}/.helper.env"
	__caddy_print_key_value "Image source" "\${CADDY_DOCKER_IMAGE:-caddy:latest}"

	printf '\nGenerated paths:\n'
	__caddy_print_key_value "root" "${docker_root} [$(__caddy_path_status "${docker_root}")]"
	__caddy_print_key_value "compose" "${docker_root}/compose.yml [$(__caddy_path_status "${docker_root}/compose.yml")]"
	__caddy_print_key_value "caddyfile" "${docker_root}/Caddyfile [$(__caddy_path_status "${docker_root}/Caddyfile")]"
	__caddy_print_key_value "helper" "${docker_root}/.caddy_helper.sh [$(__caddy_path_status "${docker_root}/.caddy_helper.sh")]"
	__caddy_print_key_value "helper env" "${docker_root}/.helper.env [$(__caddy_path_status "${docker_root}/.helper.env")]"
	__caddy_print_key_value "volume caddy_data" "caddy_data [$(__caddy_docker_volume_status "caddy_data")]"
	__caddy_print_key_value "volume caddy_config" "caddy_config [$(__caddy_docker_volume_status "caddy_config")]"
	__caddy_print_key_value "volume caddy_logs" "caddy_logs [$(__caddy_docker_volume_status "caddy_logs")]"
	if grep -Fq "caddy_site:/srv" "${docker_root}/compose.yml" 2>/dev/null; then
		__caddy_print_key_value "volume caddy_site" "caddy_site [$(__caddy_docker_volume_status "caddy_site")]"
	fi
	printf '\n'
}

# shellcheck disable=SC2032,SC2033
__caddy_run_as_caddy() {
	local command_path="$1"
	shift

	if [[ "${EUID}" -eq 0 ]]; then
		sudo -u caddy -- "${command_path}" "$@"
		return
	fi
	sudo -u caddy -- "${command_path}" "$@"
}

__caddy_binary_fix_writable_paths() {
	__caddy_as_root install -d -m 0755 /etc/caddy/logs /etc/caddy/state
	__caddy_as_root chown -R caddy:caddy /etc/caddy/logs /etc/caddy/state
}

__caddy_tail_logs() {
	local follow="${1:-0}"
	shift

	local -a files=()
	local file_path
	for file_path in "$@"; do
		[[ -f "${file_path}" ]] && files+=("${file_path}")
	done

	if [[ "${#files[@]}" -eq 0 ]]; then
		return 1
	fi

	if [[ "${follow}" == "1" ]]; then
		tail -n 100 -F "${files[@]}"
		return
	fi

	tail -n 100 "${files[@]}"
}

__caddy_parse_logs_args() {
	local log_type="${1:-}"
	local follow_flag="${2:-}"

	case "${log_type}" in
	runtime | access)
		;;
	*)
		printf 'Usage: caddy logs runtime [-f] | caddy logs access [-f]\n' >&2
		return 1
		;;
	esac

	case "${follow_flag}" in
	"")
		printf '%s %s\n' "${log_type}" "0"
		;;
	-f)
		printf '%s %s\n' "${log_type}" "1"
		;;
	*)
		printf 'Usage: caddy logs runtime [-f] | caddy logs access [-f]\n' >&2
		return 1
		;;
	esac
}

__caddy_binary_logs() {
	local log_type="$1"
	local follow="${2:-0}"
	local log_path="/etc/caddy/logs/${log_type}.log"

	if [[ -f "${log_path}" ]]; then
		if [[ "${follow}" == "1" ]]; then
			__caddy_as_root tail -n 100 -F "${log_path}"
			return 0
		fi

		__caddy_as_root tail -n 100 "${log_path}"
		return 0
	fi

	if [[ "${log_type}" == "runtime" ]]; then
		if [[ "${follow}" == "1" ]]; then
			__caddy_as_root journalctl -u caddy -n 100 -f
			return 0
		fi

		__caddy_as_root journalctl -u caddy -n 100
		return 0
	fi

	printf 'Access log file not found: %s\n' "${log_path}" >&2
	return 1
}

__caddy_docker_logs() {
	local log_type="$1"
	local follow="${2:-0}"
	local docker_root
	docker_root="$(__caddy_docker_root)"
	local log_path="/var/log/caddy/${log_type}.log"

	if [[ "${follow}" == "1" ]]; then
		local follow_status
		if (
			cd "${docker_root}" &&
				docker compose exec caddy sh -c "test -f '${log_path}' && exec tail -n 100 -F '${log_path}'"
		); then
			return 0
		else
			follow_status="$?"
		fi
		case "${follow_status}" in
		0 | 130 | 143)
			return 0
			;;
		esac
	else
		if (
			cd "${docker_root}" &&
				docker compose exec -T caddy sh -c "test -f '${log_path}' && tail -n 100 '${log_path}'"
		); then
			return 0
		fi
	fi

	if [[ "${log_type}" == "runtime" ]]; then
		(
			cd "${docker_root}" &&
				if [[ "${follow}" == "1" ]]; then
					docker compose logs caddy -n 100 -f
				else
					docker compose logs caddy -n 100
				fi
		)
		return 0
	fi

	printf 'Access log file not found: %s\n' "${log_path}" >&2
	return 1
}

__caddy_docker_require_source_config() {
	local docker_root
	local helper_env
	local source_config
	docker_root="$(__caddy_docker_root)"
	helper_env="$(__caddy_active_helper_env)" || true
	source_config="$(__caddy_docker_source_config)"
	if [[ ! -f "${source_config}" ]]; then
		cat >&2 <<EOF
Docker source Caddyfile was not found.

Resolved Docker root:
  ${docker_root}

Expected source Caddyfile:
  ${source_config}

Runtime mount:
  ./Caddyfile:/etc/caddy/Caddyfile:ro

Helper env:
  ${helper_env:-not found}

Fix:
  1. cd to the directory that contains compose.yml or compose.yaml.
  2. Make sure that directory contains a source Caddyfile.
  3. If .helper.env points to an old path, rewrite it:
     cat > .helper.env <<ENV
CADDY_MODE="docker"
CADDY_DOCKER_ROOT="\$PWD"
CADDY_EDITOR="vim"
ENV
  4. Reconfigure the shell hook:
     bash ./configure_caddy_shell.sh --mode docker --docker-root "\$PWD"
     exec \$SHELL -l
EOF
		return 1
	fi
	printf '%s\n' "${source_config}"
}

__caddy_docker_compose_sh() {
	local docker_root
	docker_root="$(__caddy_docker_root)"
	(cd "${docker_root}" && docker compose run --rm --no-deps -T --entrypoint sh caddy "$@")
}

__caddy_docker_format_source() {
	local source_config
	local temp_file
	source_config="$(__caddy_docker_require_source_config)" || return 1
	temp_file="$(mktemp)"

	if __caddy_docker_compose_sh -c 'cat > /tmp/Caddyfile && caddy fmt --overwrite /tmp/Caddyfile >/dev/null && cat /tmp/Caddyfile' <"${source_config}" >"${temp_file}"; then
		cat "${temp_file}" >"${source_config}"
		rm -f "${temp_file}"
		return 0
	fi

	rm -f "${temp_file}"
	return 1
}

__caddy_docker_validate_source() {
	local source_config
	source_config="$(__caddy_docker_require_source_config)" || return 1
	__caddy_docker_compose_sh -c 'cat > /tmp/Caddyfile && caddy validate --config /tmp/Caddyfile --adapter caddyfile' <"${source_config}"
}

__caddy_binary_control() {
	local action="$1"
	shift || true

	case "${action}" in
	up)
		__caddy_binary_fix_writable_paths
		__caddy_as_root systemctl start caddy
		;;
	down)
		__caddy_as_root systemctl stop caddy
		;;
	restart)
		__caddy_binary_fix_writable_paths
		__caddy_as_root systemctl restart caddy
		;;
	status)
		__caddy_as_root systemctl "${action}" caddy
		;;
	reload)
		__caddy_binary_fix_writable_paths
		__caddy_print_step "Formatting Caddyfile"
		__caddy_as_root "$(__caddy_binary_exec)" fmt --overwrite "$(__caddy_binary_config)"
		__caddy_print_step "Reloading systemd service"
		__caddy_as_root systemctl reload caddy
		__caddy_print_reload_complete
		;;
	fmt)
		__caddy_as_root "$(__caddy_binary_exec)" fmt --overwrite "$(__caddy_binary_config)"
		;;
	validate)
		__caddy_binary_fix_writable_paths
		__caddy_run_as_caddy "$(__caddy_binary_exec)" validate --config "$(__caddy_binary_config)" --adapter caddyfile
		;;
	version)
		__caddy_print_version_header "binary"
		__caddy_print_binary_version_details
		"$(__caddy_binary_exec)" version
		;;
	logs)
		local log_type
		local follow
		local parsed_logs
		parsed_logs="$(__caddy_parse_logs_args "${1:-}" "${2:-}")" || return 1
		read -r log_type follow <<<"${parsed_logs}"
		__caddy_binary_logs "${log_type}" "${follow}"
		;;
	edit)
		__caddy_as_root "$(__caddy_editor)" "$(__caddy_binary_config)"
		;;
	raw)
		"$(__caddy_binary_exec)" "$@"
		;;
	*)
		"$(__caddy_binary_exec)" "${action}" "$@"
		;;
	esac
}

__caddy_docker_control() {
	local action="$1"
	shift || true
	local docker_root
	docker_root="$(__caddy_docker_root)"

	case "${action}" in
	up)
		__caddy_docker_require_source_config >/dev/null &&
			(cd "${docker_root}" && docker compose up -d)
		;;
	down)
		(cd "${docker_root}" && docker compose stop)
		;;
	restart)
		__caddy_docker_require_source_config >/dev/null &&
			(cd "${docker_root}" && docker compose restart)
		;;
	status)
		(cd "${docker_root}" && docker compose ps)
		;;
	reload)
		(
			cd "${docker_root}" &&
				__caddy_print_step "Formatting source Caddyfile" &&
				__caddy_docker_format_source &&
				__caddy_print_step "Validating source Caddyfile" &&
				__caddy_docker_validate_source &&
				__caddy_print_step "Reloading container config" &&
				docker compose exec -w /etc/caddy caddy caddy reload --config /etc/caddy/Caddyfile --adapter caddyfile
		)
		__caddy_print_reload_complete
		;;
	fmt)
		__caddy_docker_format_source
		;;
	validate)
		__caddy_docker_validate_source
		;;
	version)
		__caddy_print_version_header "docker"
		__caddy_print_docker_version_details
		(cd "${docker_root}" && docker compose exec caddy caddy version)
		;;
	logs)
		local log_type
		local follow
		local parsed_logs
		parsed_logs="$(__caddy_parse_logs_args "${1:-}" "${2:-}")" || return 1
		read -r log_type follow <<<"${parsed_logs}"
		__caddy_docker_logs "${log_type}" "${follow}"
		;;
	edit)
		"$(__caddy_editor)" "$(__caddy_docker_source_config)"
		;;
	raw)
		(cd "${docker_root}" && docker compose exec -w /etc/caddy caddy caddy "$@")
		;;
	*)
		(cd "${docker_root}" && docker compose exec -w /etc/caddy caddy caddy "${action}" "$@")
		;;
	esac
}

# shellcheck disable=SC2032
caddy() {
	local action="${1:-help}"
	if [[ $# -gt 0 ]]; then
		shift
	fi

	case "${action}" in
	start | stop)
		printf 'Use caddy up or caddy down.\n' >&2
		return 1
		;;
	help | -h | --help)
		cat <<'EOF'
Usage:
  caddy up|down|restart|reload|status|edit|fmt|validate|version
  caddy logs runtime [-f]
  caddy logs access [-f]
  caddy raw <caddy-subcommand ...>

Environment:
  CADDY_MODE=binary|docker|auto
  CADDY_DOCKER_ROOT=$HOME/projects/caddy
  CADDY_EDITOR=vim
EOF
		return 0
		;;
	esac

	case "$(__caddy_resolved_mode)" in
	binary)
		__caddy_binary_control "${action}" "$@"
		;;
	docker)
		__caddy_docker_control "${action}" "$@"
		;;
	*)
		printf 'Unsupported caddy mode\n' >&2
		return 1
		;;
	esac
}

__caddy_complete() {
	local current_word="${COMP_WORDS[COMP_CWORD]}"
	mapfile -t COMPREPLY < <(compgen -W "$(__caddy_command_list)" -- "${current_word}")
}

__caddy_complete_zsh() {
	local -a commands
	commands=()
	while IFS= read -r command_name; do
		commands+=("${command_name}")
	done < <(__caddy_command_list)
	compadd -- "${commands[@]}"
}

if command -v complete >/dev/null 2>&1; then
	complete -F __caddy_complete caddy
fi

if [[ -n "${ZSH_VERSION:-}" ]]; then
	if ! typeset -f compdef >/dev/null 2>&1; then
		autoload -Uz compinit 2>/dev/null || true
		compinit -i >/dev/null 2>&1 || true
	fi
	if typeset -f compdef >/dev/null 2>&1 && [[ -z "${_comps[caddy]:-}" ]]; then
		compdef __caddy_complete_zsh caddy
	fi
fi
