#!/usr/bin/env bash

set -euo pipefail

CADDY_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HELPER_SCRIPT_URL="${HELPER_SCRIPT_URL:-https://www.mgrsc.top/http/linux/caddy/.bash_caddy}"
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/.bash_caddy"
DEFAULT_BINARY_HELPER_ENV="/etc/caddy/.helper.env"
DEFAULT_SYSTEMD_UNIT="/etc/systemd/system/caddy.service"
DEFAULT_DOCKER_IMAGE="caddy:latest"

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

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

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

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

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"
    require_command curl
    curl -fsSL "${HELPER_SCRIPT_URL}" -o "${output_path}"
}

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="$(
        . "${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
	}
}

{\$CADDY_DOMAIN:localhost} {
${site_block}
	log {
		output file ${access_log} {
			roll_size 20MiB
			roll_keep 10
			roll_keep_for 720h
		}
		format console
	}
}
EOF
}

render_compose_file() {
    local docker_root="$1"
    local static_enabled="${2:-0}"
    local conf_dir="${docker_root}/conf"
    local data_dir="${docker_root}/data"
    local config_dir="${docker_root}/config"
    local log_dir="${docker_root}/logs"
    local site_mount=""

    if [[ "${static_enabled}" == "1" ]]; then
        site_mount="      - ${docker_root}/site:/srv"
    fi

    cat <<EOF
services:
  caddy:
    image: \${CADDY_DOCKER_IMAGE:-${DEFAULT_DOCKER_IMAGE}}
    container_name: caddy
    restart: unless-stopped
    network_mode: host
    volumes:
      - ${conf_dir}:/etc/caddy
      - ${data_dir}:/data
      - ${config_dir}:/config
      - ${log_dir}:/var/log/caddy
${site_mount}
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
}

render_shell_hook() {
    local helper_path="$1"
    local helper_env_path="$2"
    cat <<EOF
# caddy-helper
export CADDY_HELPER_ENV="${helper_env_path}"
if [ -f "${helper_path}" ]; then
    source "${helper_path}"
fi
# /caddy-helper
EOF
}

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

    rendered_hook="$(render_shell_hook "${helper_path}" "${helper_env_path}")"

    touch "${rc_file}"
    if grep -Fq "${marker_begin}" "${rc_file}" 2>/dev/null; then
        local temp_file
        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_startup() {
    local helper_path="$1"
    local helper_env_path="$2"
    local shell_name
    shell_name="$(basename "${SHELL:-}")"

    case "${shell_name}" in
        zsh)
            ensure_shell_hook "${HOME}/.zshrc" "${helper_path}" "${helper_env_path}"
            ;;
        *)
            ensure_shell_hook "${HOME}/.bashrc" "${helper_path}" "${helper_env_path}"
            ;;
    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 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}'" 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}/.bash_caddy"

    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}/.bash_caddy" "${DEFAULT_BINARY_HELPER_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
  exec \$SHELL -l
  # or: source ${DEFAULT_BINARY_HELPER_SCRIPT}
EOF

    configure_shell_startup "${DEFAULT_BINARY_HELPER_SCRIPT}" "${DEFAULT_BINARY_HELPER_ENV}"
}

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

    require_command docker
    require_command curl

    install -d -m 0755 "${docker_root}"
    install -d -m 0755 "${docker_root}/conf"
    install -d -m 0755 "${docker_root}/data"
    install -d -m 0755 "${docker_root}/config"
    install -d -m 0755 "${docker_root}/logs"

    if [[ "${static_enabled}" == "1" ]]; then
        install -d -m 0755 "${docker_root}/site"
    fi

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

    cat <<EOF
Docker deployment prepared.

Files:
  ${docker_root}/compose.yml
  ${docker_root}/conf/Caddyfile
  ${docker_root}/.bash_caddy
  ${docker_root}/.helper.env

Next:
  cd ${docker_root}
  docker compose up -d
  exec \$SHELL -l
  # or: source ${docker_root}/.bash_caddy
EOF

    configure_shell_startup "${docker_root}/.bash_caddy" "${docker_root}/.helper.env"
}

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

    while [[ $# -gt 0 ]]; do
        case "$1" in
            binary|docker)
                mode="$1"
                shift
                ;;
            --mode)
                mode="${2:-}"
                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
                ;;
            --help|-h)
                usage
                return 0
                ;;
            *)
                fail "Unknown argument: $1"
                ;;
        esac
    done

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

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