#!/usr/bin/env bash

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPER_SCRIPT_URL="${HELPER_SCRIPT_URL:-https://www.mgrsc.top/http/linux/caddy/lib/caddy_helper.sh}"
SHELL_CONFIG_SCRIPT_URL="${SHELL_CONFIG_SCRIPT_URL:-https://www.mgrsc.top/http/linux/caddy/lib/shell_config.sh}"
DEFAULT_BINARY_PATH="/usr/local/bin/caddy"
DEFAULT_BINARY_CONFIG_DIR="/etc/caddy"
DEFAULT_BINARY_CADDYFILE="/etc/caddy/Caddyfile"
DEFAULT_BINARY_LOG_DIR="/etc/caddy/logs"
DEFAULT_BINARY_SITE_ROOT="/etc/caddy/site"
DEFAULT_BINARY_DATA_DIR="/etc/caddy/state"
DEFAULT_BINARY_HELPER_SCRIPT="/etc/caddy/.caddy_helper.sh"
DEFAULT_BINARY_HELPER_ENV="/etc/caddy/.helper.env"
LEGACY_BINARY_SHELL_CONFIG_SCRIPT="/etc/caddy/configure_caddy_shell.sh"
DEFAULT_SYSTEMD_UNIT="/etc/systemd/system/caddy.service"
DEFAULT_DOCKER_IMAGE="caddy:latest"

usage() {
    cat <<'EOF'
Usage:
  install_caddy.sh
  install_caddy.sh [binary|docker] [options]
  install_caddy.sh configure-shell [options]

No arguments starts an interactive setup wizard.
Arguments run non-interactive setup for automation.

Options:
  --mode <binary|docker>
  --version <version>
  --docker-root <path>
  --editor <editor>
  --enable-static-site
  --configure-shell
  --no-configure-shell
  --help
EOF
}

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

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

prompt_value() {
    local prompt="$1"
    local default_value="$2"
    local answer

    printf '%s [%s]: ' "${prompt}" "${default_value}" >&2
    read -r answer
    printf '%s\n' "${answer:-${default_value}}"
}

prompt_yes_no() {
    local prompt="$1"
    local default_value="${2:-n}"
    local answer
    local suffix

    case "${default_value}" in
        y | Y | yes | YES)
            suffix="Y/n"
            ;;
        *)
            suffix="y/N"
            ;;
    esac

    while true; do
        printf '%s [%s]: ' "${prompt}" "${suffix}" >&2
        read -r answer
        answer="${answer:-${default_value}}"

        case "${answer}" in
            y | Y | yes | YES)
                return 0
                ;;
            n | N | no | NO)
                return 1
                ;;
            *)
                stderr "Please answer yes or no."
                ;;
        esac
    done
}

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

require_command() {
    local command_name="$1"
    command -v "${command_name}" >/dev/null 2>&1 || fail "Missing required command: ${command_name}"
}

download_helper_script() {
    local output_path="$1"
    local candidate
    local candidates=(
        "${SCRIPT_DIR}/lib/caddy_helper.sh"
        "${SCRIPT_DIR}/.caddy_helper.sh"
    )

    for candidate in "${candidates[@]}"; do
        if [[ -f "${candidate}" ]]; then
            install -m 0644 "${candidate}" "${output_path}"
            return 0
        fi
    done

    require_command curl
    curl -fsSL "${HELPER_SCRIPT_URL}" -o "${output_path}"
}

run_shell_config() {
    local mode="${1:-}"
    local docker_root="${2:-}"
    local helper_path="${3:-}"
    local helper_env_path="${4:-}"
    local shell_config_script="${SCRIPT_DIR}/lib/shell_config.sh"
    local temporary_script=""
    local arguments=()
    local status

    if [[ ! -f "${shell_config_script}" ]]; then
        require_command curl
        temporary_script="$(mktemp)"
        curl -fsSL "${SHELL_CONFIG_SCRIPT_URL}" -o "${temporary_script}"
        shell_config_script="${temporary_script}"
    fi

    [[ -n "${mode}" ]] && arguments+=(--mode "${mode}")
    [[ -n "${docker_root}" ]] && arguments+=(--docker-root "${docker_root}")
    [[ -n "${helper_path}" ]] && arguments+=(--helper "${helper_path}")
    [[ -n "${helper_env_path}" ]] && arguments+=(--helper-env "${helper_env_path}")

    if (
        # shellcheck source=/dev/null
        source "${shell_config_script}"
        shell_config_main "${arguments[@]}"
    ); then
        status=0
    else
        status=$?
    fi

    [[ -z "${temporary_script}" ]] || rm -f "${temporary_script}"
    return "${status}"
}

normalize_distro() {
    local distro="${1,,}"
    case "${distro}" in
        debian | ubuntu | raspbian)
            printf 'debian\n'
            ;;
        arch | archlinux | manjaro)
            printf 'arch\n'
            ;;
        *)
            return 1
            ;;
    esac
}

detect_distro() {
    local os_release_file="${1:-/etc/os-release}"
    [[ -r "${os_release_file}" ]] || fail "Cannot read ${os_release_file}"

    local os_id
    os_id="$(
        # shellcheck source=/dev/null
        . "${os_release_file}"
        printf '%s\n' "${ID}"
    )"

    normalize_distro "${os_id}" || fail "Unsupported distro: ${os_id}. Supported: debian, ubuntu, arch"
}

detect_arch() {
    local machine_name="${1:-$(uname -m)}"
    case "${machine_name}" in
        x86_64 | amd64)
            printf 'amd64\n'
            ;;
        aarch64 | arm64)
            printf 'arm64\n'
            ;;
        armv7l)
            printf 'arm7\n'
            ;;
        armv6l)
            printf 'arm6\n'
            ;;
        *)
            fail "Unsupported architecture: ${machine_name}"
            ;;
    esac
}

docker_root_dir() {
    local home_dir="${1:-$HOME}"
    printf '%s/projects/caddy\n' "${home_dir}"
}

binary_download_url() {
    local version="$1"
    local arch="$2"
    printf 'https://github.com/caddyserver/caddy/releases/download/v%s/caddy_%s_linux_%s.tar.gz\n' "${version}" "${version}" "${arch}"
}

parse_latest_version_from_location() {
    local source_value="$1"
    local trimmed="${source_value//$'\r'/}"
    trimmed="${trimmed#location: }"
    trimmed="${trimmed#Location: }"
    trimmed="${trimmed%/}"

    if [[ "${trimmed}" =~ /v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
        printf '%s\n' "${BASH_REMATCH[1]}"
        return 0
    fi

    fail "Unable to parse latest version from: ${source_value}"
}

resolve_latest_version() {
    require_command curl

    local effective_url
    effective_url="$(
        curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/caddyserver/caddy/releases/latest"
    )"
    parse_latest_version_from_location "${effective_url}"
}

binary_permission_targets() {
    local static_enabled="${1:-0}"
    cat <<EOF
/usr/local/bin
/etc/caddy
/etc/caddy/logs
/etc/caddy/state
/etc/systemd/system
EOF
    if [[ "${static_enabled}" == "1" ]]; then
        printf '%s\n' "/etc/caddy/site"
    fi
}

ensure_binary_permissions() {
    local static_enabled="${1:-0}"
    if [[ "${EUID}" -eq 0 ]]; then
        return 0
    fi

    command -v sudo >/dev/null 2>&1 || fail "Binary mode needs root or sudo for: $(binary_permission_targets "${static_enabled}" | tr '\n' ' ')"
    sudo -v >/dev/null 2>&1 || fail "Binary mode needs sudo access for: $(binary_permission_targets "${static_enabled}" | tr '\n' ' ')"
}

render_caddyfile() {
    local mode="$1"
    local static_enabled="${2:-0}"
    local runtime_log
    local access_log
    local storage_block=""
    local site_block

    case "${mode}" in
        binary)
            runtime_log="/etc/caddy/logs/runtime.log"
            access_log="/etc/caddy/logs/access.log"
            storage_block="	storage file_system /etc/caddy/state"
            ;;
        docker)
            runtime_log="/var/log/caddy/runtime.log"
            access_log="/var/log/caddy/access.log"
            ;;
        *)
            fail "Unsupported render mode: ${mode}"
            ;;
    esac

    if [[ "${static_enabled}" == "1" ]]; then
        if [[ "${mode}" == "binary" ]]; then
            site_block="$(
                cat <<'EOF'
	root * /etc/caddy/site
	file_server
EOF
            )"
        else
            site_block="$(
                cat <<'EOF'
	root * /srv
	file_server
EOF
            )"
        fi
    else
        site_block='	respond "Caddy is running" 200'
    fi

    cat <<EOF
{
${storage_block}
	log default {
		output file ${runtime_log} {
			roll_size 20MiB
			roll_keep 10
			roll_keep_for 720h
		}
		format console
		level INFO
		exclude http.log.access
	}
}

(site_logs) {
	log {
		output file ${access_log} {
			roll_size 20MiB
			roll_keep 10
			roll_keep_for 720h
		}
		format json
	}
}

{\$CADDY_DOMAIN:localhost} {
	import site_logs
${site_block}
}
EOF
}

render_compose_file() {
    local static_enabled="${1:-0}"
    local site_mount=""
    local site_volume=""

    if [[ "${static_enabled}" == "1" ]]; then
        site_mount="      - caddy_site:/srv"
        site_volume="
  caddy_site:
    name: caddy_site"
    fi

    cat <<EOF
services:
  caddy:
    image: \${CADDY_DOCKER_IMAGE:-${DEFAULT_DOCKER_IMAGE}}
    container_name: caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
      - caddy_logs:/var/log/caddy
${site_mount}

volumes:
  caddy_data:
    name: caddy_data
  caddy_config:
    name: caddy_config
  caddy_logs:
    name: caddy_logs${site_volume}
EOF
}

render_systemd_service() {
    cat <<EOF
[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/local/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
ReadWritePaths=/etc/caddy/logs /etc/caddy/state
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target
EOF
}

render_helper_env() {
    local deployment_mode="${1:-binary}"
    local docker_root="${2:-}"
    local editor_name="${3:-vim}"
    cat <<EOF
CADDY_MODE="${deployment_mode}"
CADDY_DOCKER_ROOT="${docker_root}"
CADDY_EDITOR="${editor_name}"
EOF
}

print_install_summary() {
    local mode="$1"
    local version="$2"
    local docker_root="$3"
    local static_enabled="$4"
    local editor_name="$5"
    local configure_shell="$6"
    local static_status="disabled"

    if [[ "${static_enabled}" == "1" ]]; then
        static_status="enabled"
    fi

    cat <<EOF
Install summary:

Mode:
  ${mode}

Editor:
  ${editor_name}

Static site:
  ${static_status}

Shell helper:
  $([[ "${configure_shell}" == "1" ]] && printf 'enabled' || printf 'disabled')
EOF

    case "${mode}" in
        binary)
            cat <<EOF

Version:
  ${version:-latest}

Writes:
  ${DEFAULT_BINARY_PATH}
  ${DEFAULT_BINARY_CADDYFILE}
  ${DEFAULT_BINARY_HELPER_SCRIPT}
  ${DEFAULT_BINARY_HELPER_ENV}
  ${DEFAULT_SYSTEMD_UNIT}

Requires:
  root or sudo access
EOF
            ;;
        docker)
            cat <<EOF

Docker root:
  ${docker_root}

Writes:
  ${docker_root}/compose.yml
  ${docker_root}/Caddyfile
  ${docker_root}/.caddy_helper.sh
  ${docker_root}/.helper.env

Named volumes:
  caddy_data
  caddy_config
  caddy_logs
EOF
            if [[ "${static_enabled}" == "1" ]]; then
                printf '  caddy_site\n'
            fi
            ;;
    esac
}

ensure_caddy_account() {
    if ! getent group caddy >/dev/null 2>&1; then
        run_as_root groupadd --system caddy
    fi

    if ! id -u caddy >/dev/null 2>&1; then
        run_as_root useradd \
            --system \
            --gid caddy \
            --create-home \
            --home-dir "${DEFAULT_BINARY_DATA_DIR}" \
            --shell /usr/sbin/nologin \
            --comment "Caddy web server" \
            caddy
    fi
}

write_root_owned_file() {
    local target_path="$1"
    local temp_file

    temp_file="$(mktemp)"
    cat >"${temp_file}"
    run_as_root install -m 0644 "${temp_file}" "${target_path}"
    rm -f "${temp_file}"
}

cleanup_temp_dir() {
    local target_dir="${1:-}"
    if [[ -n "${target_dir}" && -d "${target_dir}" ]]; then
        rm -rf "${target_dir}"
    fi
}

install_binary() {
    local version="$1"
    local static_enabled="${2:-0}"
    local editor_name="${3:-vim}"
    local configure_shell="${4:-1}"
    local distro
    local arch
    local download_url
    local temp_dir
    local resolved_version

    distro="$(detect_distro)"
    arch="$(detect_arch)"
    ensure_binary_permissions "${static_enabled}"

    if [[ -n "${version}" ]]; then
        resolved_version="${version}"
    else
        resolved_version="$(resolve_latest_version)"
    fi

    download_url="$(binary_download_url "${resolved_version}" "${arch}")"

    require_command curl
    require_command tar
    require_command systemctl
    require_command groupadd
    require_command useradd
    require_command install

    temp_dir="$(mktemp -d)"
    trap 'cleanup_temp_dir "${temp_dir}"; trap - RETURN' RETURN

    stderr "Detected distro family: ${distro}"
    stderr "Downloading Caddy v${resolved_version} for linux/${arch}"

    curl -fsSL "${download_url}" -o "${temp_dir}/caddy.tar.gz"
    tar -xzf "${temp_dir}/caddy.tar.gz" -C "${temp_dir}"
    download_helper_script "${temp_dir}/.caddy_helper.sh"

    ensure_caddy_account
    run_as_root install -d -m 0755 "${DEFAULT_BINARY_CONFIG_DIR}"
    run_as_root install -d -m 0755 "${DEFAULT_BINARY_LOG_DIR}"
    run_as_root install -d -m 0755 "${DEFAULT_BINARY_DATA_DIR}"
    run_as_root install -m 0755 "${temp_dir}/caddy" "${DEFAULT_BINARY_PATH}"
    run_as_root install -m 0644 "${temp_dir}/.caddy_helper.sh" "${DEFAULT_BINARY_HELPER_SCRIPT}"
    run_as_root rm -f "${LEGACY_BINARY_SHELL_CONFIG_SCRIPT}"

    if [[ "${static_enabled}" == "1" ]]; then
        run_as_root install -d -m 0755 "${DEFAULT_BINARY_SITE_ROOT}"
    fi

    render_caddyfile binary "${static_enabled}" | write_root_owned_file "${DEFAULT_BINARY_CADDYFILE}"
    render_helper_env "binary" "" "${editor_name}" | write_root_owned_file "${DEFAULT_BINARY_HELPER_ENV}"
    render_systemd_service | write_root_owned_file "${DEFAULT_SYSTEMD_UNIT}"

    run_as_root chown -R caddy:caddy "${DEFAULT_BINARY_DATA_DIR}"
    run_as_root chown -R caddy:caddy "${DEFAULT_BINARY_LOG_DIR}"
    run_as_root systemctl daemon-reload

    cat <<EOF
Binary deployment prepared.

Files:
  ${DEFAULT_BINARY_PATH}
  ${DEFAULT_BINARY_CADDYFILE}
  ${DEFAULT_BINARY_HELPER_SCRIPT}
  ${DEFAULT_BINARY_HELPER_ENV}
  ${DEFAULT_SYSTEMD_UNIT}

Version:
  ${resolved_version}

Next:
  sudo systemctl enable --now caddy
EOF

    if [[ "${configure_shell}" == "1" ]]; then
        run_shell_config \
            binary \
            "" \
            "${DEFAULT_BINARY_HELPER_SCRIPT}" \
            "${DEFAULT_BINARY_HELPER_ENV}"
    else
        cat <<EOF

Shell helper skipped. Load it for the current session with:
  source ${DEFAULT_BINARY_HELPER_SCRIPT}
EOF
    fi
}

install_docker() {
    local docker_root="$1"
    local static_enabled="${2:-0}"
    local editor_name="${3:-vim}"
    local configure_shell="${4:-1}"

    require_command docker
    require_command curl

    install -d -m 0755 "${docker_root}"
    rm -f "${docker_root}/configure_caddy_shell.sh"

    render_caddyfile docker "${static_enabled}" >"${docker_root}/Caddyfile"
    render_compose_file "${static_enabled}" >"${docker_root}/compose.yml"
    download_helper_script "${docker_root}/.caddy_helper.sh"
    render_helper_env "docker" "${docker_root}" "${editor_name}" >"${docker_root}/.helper.env"

    cat <<EOF
Docker deployment prepared.

Files:
  ${docker_root}/compose.yml
  ${docker_root}/Caddyfile
  ${docker_root}/.caddy_helper.sh
  ${docker_root}/.helper.env

Next:
  cd ${docker_root}
EOF

    if [[ "${configure_shell}" == "1" ]]; then
        run_shell_config \
            docker \
            "${docker_root}" \
            "${docker_root}/.caddy_helper.sh" \
            "${docker_root}/.helper.env"
        cat <<'EOF'
  exec $SHELL -l
  caddy up
EOF
    else
        cat <<EOF
  source ${docker_root}/.caddy_helper.sh
  caddy up
EOF
    fi
}

interactive_main() {
    local mode
    local version=""
    local docker_root="${CADDY_DOCKER_ROOT:-$(docker_root_dir)}"
    local static_enabled="0"
    local editor_name="vim"
    local configure_shell="1"

    cat <<'EOF'
Caddy setup wizard

Press Enter to accept defaults.
EOF

    while true; do
        mode="$(prompt_value "Deployment mode (binary/docker)" "docker")"
        case "${mode}" in
            binary | docker)
                break
                ;;
            *)
                stderr "Unsupported mode: ${mode}"
                ;;
        esac
    done

    editor_name="$(prompt_value "Editor for caddy edit" "${editor_name}")"

    if [[ "${mode}" == "binary" ]]; then
        version="$(prompt_value "Caddy version for binary mode" "latest")"
        if [[ "${version}" == "latest" ]]; then
            version=""
        fi
    else
        docker_root="$(prompt_value "Docker root" "${docker_root}")"
    fi

    if prompt_yes_no "Enable static site support" "n"; then
        static_enabled="1"
    fi

    if ! prompt_yes_no "Configure shell helper" "y"; then
        configure_shell="0"
    fi

    printf '\n'
    print_install_summary "${mode}" "${version}" "${docker_root}" "${static_enabled}" "${editor_name}" "${configure_shell}"
    printf '\n'

    if ! prompt_yes_no "Continue with this setup" "n"; then
        stderr "Cancelled."
        return 1
    fi

    case "${mode}" in
        binary)
            install_binary "${version}" "${static_enabled}" "${editor_name}" "${configure_shell}"
            ;;
        docker)
            install_docker "${docker_root}" "${static_enabled}" "${editor_name}" "${configure_shell}"
            ;;
    esac
}

main() {
    local mode="binary"
    local version=""
    local docker_root="${CADDY_DOCKER_ROOT:-$(docker_root_dir)}"
    local static_enabled="0"
    local editor_name="vim"
    local configure_shell="1"
    local command_name="install"
    local mode_explicit="0"

    if [[ $# -eq 0 ]]; then
        interactive_main
        return
    fi

    while [[ $# -gt 0 ]]; do
        case "$1" in
            configure-shell)
                command_name="configure-shell"
                shift
                ;;
            binary | docker)
                mode="$1"
                mode_explicit="1"
                shift
                ;;
            --mode)
                mode="${2:-}"
                mode_explicit="1"
                shift 2
                ;;
            --version)
                version="${2:-}"
                shift 2
                ;;
            --docker-root)
                docker_root="${2:-}"
                shift 2
                ;;
            --editor)
                editor_name="${2:-}"
                shift 2
                ;;
            --enable-static-site)
                static_enabled="1"
                shift
                ;;
            --configure-shell)
                configure_shell="1"
                shift
                ;;
            --no-configure-shell)
                configure_shell="0"
                shift
                ;;
            --help | -h)
                usage
                return 0
                ;;
            *)
                fail "Unknown argument: $1"
                ;;
        esac
    done

    if [[ "${command_name}" == "configure-shell" ]]; then
        if [[ "${mode_explicit}" == "0" && -n "${docker_root}" ]]; then
            mode="docker"
        fi
        if [[ "${mode}" == "docker" ]]; then
            run_shell_config \
                docker \
                "${docker_root}" \
                "${docker_root}/.caddy_helper.sh" \
                "${docker_root}/.helper.env"
        else
            run_shell_config \
                binary \
                "" \
                "${DEFAULT_BINARY_HELPER_SCRIPT}" \
                "${DEFAULT_BINARY_HELPER_ENV}"
        fi
        return
    fi

    case "${mode}" in
        binary)
            install_binary "${version}" "${static_enabled}" "${editor_name}" "${configure_shell}"
            ;;
        docker)
            install_docker "${docker_root}" "${static_enabled}" "${editor_name}" "${configure_shell}"
            ;;
        *)
            fail "Unsupported mode: ${mode}"
            ;;
    esac
}

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