#!/usr/bin/env bash

set -Eeuo pipefail
IFS=$'\n\t'

PHP_PATH="/www/server/php/83"
EXT_DIR="${PHP_PATH}/lib/php/extensions/no-debug-non-zts-20230831"
PHP_INI="${PHP_PATH}/etc/php.ini"
PHP_CLI_INI="${PHP_PATH}/etc/php-cli.ini"
#PHP_CLI_BAK="${PHP_PATH}/etc/php-cli.bak"
SUPERVISOR_DIR="/etc/supervisord.d"
NGINX_REWRITE_DIR="/www/server/panel/vhost/rewrite"
NGINX_VHOST_DIR="/www/server/panel/vhost/nginx"
MODULE_BASE_URL="https://downloads.yunzmall.com/bt_php_modules"
VENDOR_URL="https://downloads.yunzmall.com/framework-yun_shop_free.zip"
VENDOR_ZIP="/tmp/framework-yun_shop_free.zip"
LOG_FILE="/tmp/php83_upgrade.log"

QUIET=false
INSTALL_IMAGICK=true

usage() {
    echo "用法: $0 <域名> <网站路径> [--quiet] [--with-imagick|--without-imagick]"
}

log() {
    if [[ "${QUIET}" != "true" ]]; then
        echo "$@"
    else
        printf '%s\n' "$*" >>"${LOG_FILE}"
    fi
}

die() {
    echo "错误: $*" >&2
    exit 1
}

setup_logging() {
    local args_text="(无参数)"

    command -v tee >/dev/null 2>&1 || die "缺少命令: tee"
    touch "${LOG_FILE}" || die "无法写入日志文件: ${LOG_FILE}"

    local arg
    for arg in "$@"; do
        [[ "${arg}" == "--quiet" ]] && QUIET=true
    done

    if [[ "$#" -gt 0 ]]; then
        printf -v args_text '%q ' "$@"
        args_text="${args_text% }"
    fi

    exec > >(tee -a "${LOG_FILE}") 2>&1
    log ""
    log "[$(date '+%F %T')] php83_upgrade.sh 开始执行: ${args_text}"
}

finish_logging() {
    local status=$?

    if [[ "${status}" -eq 0 ]]; then
        log "[$(date '+%F %T')] php83_upgrade.sh 执行完成"
    else
        log "[$(date '+%F %T')] php83_upgrade.sh 执行失败，退出码: ${status}"
    fi

    return "${status}"
}

require_cmd() {
    command -v "$1" >/dev/null 2>&1 || die "缺少命令: $1"
}

download_file() {
    local url="$1"
    local target="$2"
    local tmp="${target}.tmp.$$"

    rm -f "${tmp}"
    curl -fL --connect-timeout 5 --retry 3 --retry-delay 2 -o "${tmp}" "${url}" \
        || {
            rm -f "${tmp}"
            die "下载失败: ${url}"
        }
    mv "${tmp}" "${target}"
}

backup_once() {
    local file="$1"

    if [[ -f "${file}" && ! -f "${file}.bak" ]]; then
        cp -a "${file}" "${file}.bak"
    fi
}

ensure_ini_value() {
    local key="$1"
    local value="$2"
    local file="$3"

    if grep -Eq "^[[:space:]]*;?[[:space:]]*${key}[[:space:]]*=" "${file}"; then
        sed -i -E "s#^[[:space:]]*;?[[:space:]]*${key}[[:space:]]*=.*#${key} = ${value}#" "${file}"
    else
        printf '%s = %s\n' "${key}" "${value}" >>"${file}"
    fi
}

ensure_env_value() {
    local key="$1"
    local value="$2"
    local file="$3"

    if grep -Eq "^[[:space:]]*${key}=" "${file}"; then
        sed -i -E "s#^[[:space:]]*${key}=.*#${key}=${value}#" "${file}"
    else
        if [[ -s "${file}" ]] && [[ "$(tail -c 1 "${file}" | wc -l | tr -d ' ')" -eq 0 ]]; then
            printf '\n' >>"${file}"
        fi
        printf '%s=%s\n' "${key}" "${value}" >>"${file}"
    fi
}

ensure_line() {
    local line="$1"
    local file="$2"

    grep -Fxq "${line}" "${file}" || printf '%s\n' "${line}" >>"${file}"
}

validate_args() {
    if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
        usage
        exit 0
    fi

    [[ "$#" -ge 2 ]] || {
        usage
        die "域名和网站路径不能为空"
    }

    DOMAIN="$1"
    WEB_PATH="$2"
    shift 2

    while [[ "$#" -gt 0 ]]; do
        case "$1" in
            --quiet)
                QUIET=true
                ;;
            --with-imagick|--install-imagick)
                INSTALL_IMAGICK=true
                ;;
            --without-imagick|--no-imagick|--skip-imagick)
                INSTALL_IMAGICK=false
                ;;
            --imagick)
                [[ "$#" -ge 2 ]] || die "参数 --imagick 需要 true 或 false"
                case "$2" in
                    true|yes|1|on)
                        INSTALL_IMAGICK=true
                        ;;
                    false|no|0|off)
                        INSTALL_IMAGICK=false
                        ;;
                    *)
                        die "参数 --imagick 只支持 true 或 false"
                        ;;
                esac
                shift
                ;;
            --imagick=*)
                case "${1#*=}" in
                    true|yes|1|on)
                        INSTALL_IMAGICK=true
                        ;;
                    false|no|0|off)
                        INSTALL_IMAGICK=false
                        ;;
                    *)
                        die "参数 --imagick 只支持 true 或 false"
                        ;;
                esac
                ;;
            *)
                usage
                die "未知参数: $1"
                ;;
        esac
        shift
    done

    [[ "${EUID}" -eq 0 ]] || die "请使用 root 用户执行"
    #[[ "${DOMAIN}" != *"/"* && "${DOMAIN}" != *".."* ]] || die "域名参数不合法: ${DOMAIN}"
    [[ "${DOMAIN}" =~ ^[A-Za-z0-9._-]+$ ]] || die "域名参数不合法: ${DOMAIN}"
    [[ -d "${WEB_PATH}" ]] || die "网站路径不存在: ${WEB_PATH}"
    [[ -f "${WEB_PATH}/.env" ]] || die "缺少环境配置文件: ${WEB_PATH}/.env"
    [[ -x "${PHP_PATH}/bin/php" ]] || die "PHP 8.3 不存在或不可执行: ${PHP_PATH}/bin/php"
    [[ -d "${EXT_DIR}" ]] || die "PHP 扩展目录不存在: ${EXT_DIR}"
    [[ -f "${PHP_INI}" ]] || die "php.ini 不存在: ${PHP_INI}"
    [[ -d "${SUPERVISOR_DIR}" ]] || die "supervisor 配置目录不存在: ${SUPERVISOR_DIR}"
    [[ -d "${NGINX_REWRITE_DIR}" ]] || die "nginx rewrite 配置目录不存在: ${NGINX_REWRITE_DIR}"
    [[ -d "${NGINX_VHOST_DIR}" ]] || die "nginx 主配置目录不存在: ${NGINX_VHOST_DIR}"

    SUPERVISOR_CONF="${SUPERVISOR_DIR}/${DOMAIN}.ini"
    NGINX_REWRITE_CONF="${NGINX_REWRITE_DIR}/${DOMAIN}.conf"
    NGINX_MAIN_CONF="${NGINX_VHOST_DIR}/${DOMAIN}.conf"
    [[ -f "${SUPERVISOR_CONF}" ]] || die "supervisor 配置不存在: ${SUPERVISOR_CONF}"
}

check_required_php_modules() {
    local required_modules=("fileinfo" "igbinary" "Zend OPcache" "intl" "redis" "mbstring")
    local installed_modules
    local mod
    local mod_lower

    installed_modules="$("${PHP_PATH}/bin/php" -m 2>/dev/null | tr '[:upper:]' '[:lower:]')"

    for mod in "${required_modules[@]}"; do
        mod_lower="$(printf '%s' "${mod}" | tr '[:upper:]' '[:lower:]')"
        if ! grep -Fxq "${mod_lower}" <<<"${installed_modules}"; then
            die "缺少必要的 PHP 模块: ${mod}"
        fi
    done
}

install_system_dependencies() {
    require_cmd dnf

    dnf install -y epel-release ImageMagick-devel \
        || die "网络出问题、软件源不可用或系统已被官方废弃，无法安装依赖"
}

install_php_extensions() {
    local modules=("swoole" "mongodb")
    local module
    local extension_line

    if [[ "${INSTALL_IMAGICK}" == "true" ]]; then
        modules+=("imagick")
    fi

    for module in "${modules[@]}"; do
        log "安装 PHP 扩展: ${module}"
        download_file "${MODULE_BASE_URL}/${module}.so" "${EXT_DIR}/${module}.so"

        extension_line="extension = ${EXT_DIR}/${module}.so"
        if grep -Eq "^[[:space:]]*extension[[:space:]]*=.*${module}\.so" "${PHP_INI}"; then
            log "${module}.so 已存在于 php.ini"
        else
            ensure_line "${extension_line}" "${PHP_INI}"
        fi
    done
}

configure_imagick() {
    local policy_path="/etc/ImageMagick-6/policy.xml"

    [[ -d "$(dirname "${policy_path}")" ]] || die "ImageMagick 配置目录不存在: $(dirname "${policy_path}")"
    backup_once "${policy_path}"
    download_file "${MODULE_BASE_URL}/policy.xml" "${policy_path}"
}

configure_php_ini() {
    backup_once "${PHP_INI}"

    ensure_ini_value "disable_functions" "exec,system,chroot,chgrp,chown,shell_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,fsocket,eval" "${PHP_INI}"
    ensure_ini_value "opcache.memory_consumption" "512" "${PHP_INI}"
    ensure_ini_value "post_max_size" "350M" "${PHP_INI}"
    ensure_ini_value "upload_max_filesize" "300M" "${PHP_INI}"
    ensure_ini_value "max_execution_time" "600" "${PHP_INI}"
    ensure_ini_value "memory_limit" "-1" "${PHP_INI}"
    ensure_ini_value "max_input_time" "600" "${PHP_INI}"
    ensure_ini_value "max_input_vars" "10000" "${PHP_INI}"

    #if [[ -f "${PHP_CLI_INI}" ]]; then
    #    mv "${PHP_CLI_INI}" "${PHP_CLI_BAK}"
    #else
    #    log "未找到 php-cli.ini，跳过 CLI 配置备份"
    #fi
}

configure_env() {
    local proc_count

    require_cmd nproc
    proc_count="$(( $(nproc) * 2 ))"

    backup_once "${WEB_PATH}/.env"
    ensure_env_value "IS_SWOOLE" "true" "${WEB_PATH}/.env"
    ensure_env_value "LARAVELS_LISTEN_IP" "'0.0.0.0'" "${WEB_PATH}/.env"
    ensure_env_value "LARAVELS_LISTEN_PORT" "'5200'" "${WEB_PATH}/.env"
    ensure_env_value "LARAVELS_WORKER_NUM" "'${proc_count}'" "${WEB_PATH}/.env"
    chmod +x "${WEB_PATH}/swoole.sh"
}

configure_supervisor() {
    local program_name="${DOMAIN}_swoole"

    backup_once "${SUPERVISOR_CONF}"

    sed -i -E "s#${PHP_PATH%/83}/74#${PHP_PATH}#g; s#php-fpm-74#php-fpm-83#g; s#php/74#php/83#g; s#^[[:space:]]*user[[:space:]]*=.*#user=root#g" "${SUPERVISOR_CONF}"

    if grep -Fq "swoole.sh" "${SUPERVISOR_CONF}"; then
        log "supervisor 已配置 swoole"
        return
    fi

    cat >>"${SUPERVISOR_CONF}" <<EOF

[program:${program_name}]
process_name=%(program_name)s
command=${WEB_PATH}/swoole.sh ${PHP_PATH}/bin/php
autostart=true
autorestart=true
stopsignal=TERM
stopasgroup=true
user=root
numprocs=1
redirect_stderr=true
stdout_logfile_maxbytes=10MB
stdout_logfile_backups=5
stdout_logfile=${WEB_PATH}/storage/logs/swoole.log
EOF
}

configure_nginx() {
    local rewrite_tmp="${NGINX_REWRITE_CONF}.tmp.$$"
    local main_tmp="${NGINX_MAIN_CONF}.tmp.$$"
    local add_static_config=true

    cat >"${rewrite_tmp}" <<'EOF'
# pc端前端
location /plugins/shop_server/ {
  proxy_http_version 1.1;
  proxy_connect_timeout 300s;
  proxy_send_timeout 900s;
  proxy_read_timeout 900s;
  proxy_buffer_size 512k;
  proxy_buffers 4 512k;
  proxy_busy_buffers_size 512k;
  proxy_temp_file_write_size 512k;
  proxy_max_temp_file_size 0;
  proxy_redirect off;
  proxy_hide_header Vary;
  proxy_set_header Connection "";
  proxy_set_header Accept-Encoding '';
  proxy_set_header Referer $http_referer;
  proxy_set_header Cookie $http_cookie;
  proxy_set_header Host $http_host;
  proxy_set_header Scheme $scheme;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Real-PORT $remote_port;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Server-Protocol $server_protocol;
  proxy_set_header Server-Name $server_name;
  proxy_set_header Server-Addr $server_addr;
  proxy_set_header Server-Port $server_port;
  proxy_pass http://127.0.0.1:3000;
}

# ws config
location /ws
{
  proxy_pass http://127.0.0.1:8181;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";
  proxy_set_header X-Real-IP $remote_addr;
}

location /ws_msg
{
  proxy_pass http://127.0.0.1:8182;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";
  proxy_set_header X-Real-IP $remote_addr;
}

# swoole config
location @laravels {
  proxy_http_version 1.1;
  proxy_connect_timeout 300s;
  proxy_send_timeout 900s;
  proxy_read_timeout 900s;
  proxy_buffer_size 512k;
  proxy_buffers 4 512k;
  proxy_busy_buffers_size 512k;
  proxy_temp_file_write_size 512k;
  proxy_max_temp_file_size 0;
  proxy_redirect off;
  proxy_hide_header Vary;
  proxy_set_header Connection "";
  proxy_set_header Accept-Encoding '';
  proxy_set_header Referer $http_referer;
  proxy_set_header Cookie $http_cookie;
  proxy_set_header Host $http_host;
  proxy_set_header Scheme $scheme;
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Real-PORT $remote_port;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Server-Protocol $server_protocol;
  proxy_set_header Server-Name $server_name;
  proxy_set_header Server-Addr $server_addr;
  proxy_set_header Server-Port $server_port;
  proxy_pass http://127.0.0.1:5200;
}

#前台接口
location = /addons/yun_shop/api.php {
  try_files "" @laravels;
}
#前台入口
location = /addons/yun_shop/ {
  try_files $uri $uri/ /addons/yun_shop/index.html$is_args$args;
}
#首页接口
location = /officialwebsite.php {
  try_files "" @laravels;
}
# 企业后台接口
location ~ /business/[0-9]+ {
  try_files "" @laravels;
}
# default
location / {
  try_files $uri @laravels;
}
location ~ \.php$ {
  try_files "" @laravels;
}
EOF

    if [[ -f "${NGINX_REWRITE_CONF}" ]] && cmp -s "${rewrite_tmp}" "${NGINX_REWRITE_CONF}"; then
        rm -f "${rewrite_tmp}"
        log "nginx rewrite 配置已是目标内容: ${NGINX_REWRITE_CONF}"
    else
        backup_once "${NGINX_REWRITE_CONF}"
        mv "${rewrite_tmp}" "${NGINX_REWRITE_CONF}"
        log "已更新 nginx rewrite 配置: ${NGINX_REWRITE_CONF}"
    fi

    if [[ ! -f "${NGINX_MAIN_CONF}" ]]; then
        log "未找到 nginx 主配置，跳过主配置调整: ${NGINX_MAIN_CONF}"
        return
    fi

    if grep -Eq '^[[:space:]]*# static file config[[:space:]]*$' "${NGINX_MAIN_CONF}"; then
        add_static_config=false
    fi

    awk -v add_static_config="${add_static_config}" -v WEB_PATH="${WEB_PATH}" '
function brace_delta(src,    line, opens, closes) {
    line = src
    opens = gsub(/\{/, "{", line)
    line = src
    closes = gsub(/\}/, "}", line)
    if (opens > 0) {
        seen_open = 1
    }
    return opens - closes
}
{
    if (!skipping && $0 ~ /^[[:space:]]*location[[:space:]]+((=|~\*|~|\^~)[[:space:]]+)?\/plugins\/shop_server\/?([[:space:]]|\{)/) {
        skipping = 1
        seen_open = 0
        depth = 0
        depth += brace_delta($0)
        if (seen_open && depth <= 0) {
            skipping = 0
        }
        next
    }

    if (skipping) {
        depth += brace_delta($0)
        if (seen_open && depth <= 0) {
            skipping = 0
        }
        next
    }

    line = $0
    if (line ~ /^[[:space:]]*include[[:space:]]+enable-php-(74|83)\.conf[[:space:]]*;[[:space:]]*(#.*)?$/) {
        sub(/[^[:space:]]/, "# &", line)
    }
    print line

    if (add_static_config == "true" && !static_config_inserted && line ~ /^[[:space:]]*#ERROR-PAGE-END[[:space:]]*$/) {
        indent = line
        sub(/#ERROR-PAGE-END[[:space:]]*$/, "", indent)
        print ""
        print indent "# static file config"
        print indent "location /addons/yun_shop/static/ { root " WEB_PATH ";}"
        print ""
        print indent "location /static/ {root " WEB_PATH ";}"
        print ""
        print indent "location /business/ {root " WEB_PATH ";}"
        print ""
        print indent "location = /favicon.ico { log_not_found off;}"
        static_config_inserted = 1
    }
}
END {
    if (skipping || (add_static_config == "true" && !static_config_inserted)) {
        exit 1
    }
}
' "${NGINX_MAIN_CONF}" >"${main_tmp}" || {
        rm -f "${main_tmp}"
        die "更新 nginx 主配置失败，请确认存在 #ERROR-PAGE-END 标记: ${NGINX_MAIN_CONF}"
    }

    if cmp -s "${main_tmp}" "${NGINX_MAIN_CONF}"; then
        rm -f "${main_tmp}"
        log "nginx 主配置无需调整: ${NGINX_MAIN_CONF}"
    else
        backup_once "${NGINX_MAIN_CONF}"
        mv "${main_tmp}" "${NGINX_MAIN_CONF}"
        log "已更新 nginx 主配置: ${NGINX_MAIN_CONF}"
    fi
}

install_vendor() {
    local unzip_status

    require_cmd unzip
    rm -rf "${WEB_PATH}/vendor/"
    download_file "${VENDOR_URL}" "${VENDOR_ZIP}"

    if unzip -q -o "${VENDOR_ZIP}" -d "${WEB_PATH}/" -x ".env"; then
        unzip_status=0
    else
        unzip_status=$?
    fi
    if [[ "${unzip_status}" -ne 0 && "${unzip_status}" -ne 11 ]]; then
        die "商城完整包解压失败: ${VENDOR_ZIP}"
    fi
    rm -rf "${WEB_PATH}/bootstrap/cache/"*
}

remove_user_ini() {
    local user_ini="${WEB_PATH}/.user.ini"

    [[ -e "${user_ini}" ]] || return 0

    require_cmd chattr
    log "删除网站路径下的 .user.ini: ${user_ini}"
    chattr -i "${user_ini}" || die "移除 .user.ini 的 immutable 属性失败: ${user_ini}"
    rm -f "${user_ini}" || die "删除 .user.ini 失败: ${user_ini}"
}

test_nginx_config() {
    if [[ -x "/www/server/nginx/sbin/nginx" ]]; then
        /www/server/nginx/sbin/nginx -t
    else
        nginx -t
    fi
}

restart_services() {
    /etc/init.d/php-fpm-83 restart
    systemctl restart supervisord
    test_nginx_config || die "nginx 配置检测失败"
    /etc/init.d/nginx restart
}

main() {
    setup_logging "$@"
    trap finish_logging EXIT
    rm -rf ${PHP_CLI_INI}
    validate_args "$@"
    log "Upgrade php83"
    require_cmd curl
    require_cmd grep
    require_cmd sed
    require_cmd awk
    require_cmd cmp
    require_cmd systemctl

    check_required_php_modules
    if [[ "${INSTALL_IMAGICK}" == "true" ]]; then
        install_system_dependencies
    else
        log "跳过 imagick、EPEL 和 ImageMagick 依赖安装"
    fi
    install_php_extensions
    if [[ "${INSTALL_IMAGICK}" == "true" ]]; then
        configure_imagick
    else
        log "跳过 ImageMagick 配置"
    fi
    configure_php_ini
    configure_env
    configure_supervisor
    configure_nginx
    test_nginx_config || die "nginx 配置检测失败"
    install_vendor
    remove_user_ini
    log "设置网站路径权限为 777: ${WEB_PATH}"
    chmod -R 777 "${WEB_PATH}" || die "设置网站路径权限失败: ${WEB_PATH}"
    restart_services

    log "PHP 8.3 升级完成"
}

main "$@"

