#!/bin/bash

# ==============================================================================
# zanpiancms 一键部署脚本
# ==============================================================================

# 通过系统包管理器安装软件包
install_pkg() {
    local pkg="$1"
    if command -v apt-get &> /dev/null; then
        sudo apt-get update -qq && sudo apt-get install -y "$pkg"
    elif command -v dnf &> /dev/null; then
        sudo dnf install -y "$pkg"
    elif command -v yum &> /dev/null; then
        sudo yum install -y "$pkg"
    elif command -v apk &> /dev/null; then
        sudo apk add --no-cache "$pkg"
    else
        return 1
    fi
}

# 确保命令可用；缺失时安装并等待就绪
ensure_command() {
    local cmd="$1"
    local pkg="${2:-$cmd}"

    if command -v "$cmd" &> /dev/null; then
        return 0
    fi

    echo "未检测到 $cmd，正在安装..."
    if ! install_pkg "$pkg"; then
        echo "错误: $cmd 安装失败，请手动安装后重试。"
        echo "  Debian/Ubuntu: sudo apt-get install -y $pkg"
        echo "  CentOS/RHEL:     sudo yum install -y $pkg"
        exit 1
    fi

    local i=0
    while ! command -v "$cmd" &> /dev/null; do
        sleep 1
        i=$((i + 1))
        if [ $i -ge 30 ]; then
            echo "错误: $cmd 安装后仍不可用，请检查环境。"
            exit 1
        fi
    done
    echo "$cmd 已就绪。"
}

# 等待 Docker 守护进程可用
wait_for_docker_daemon() {
    local i=0
    while ! docker info &> /dev/null; do
        sleep 2
        i=$((i + 1))
        if [ $i -ge 60 ]; then
            echo "错误: Docker 服务启动超时，请手动检查。"
            exit 1
        fi
    done
}

# 确保 Docker 及 Compose 已安装并就绪
ensure_docker() {
    if command -v docker &> /dev/null && docker info &> /dev/null; then
        echo "Docker 环境已存在。"
    elif ! command -v docker &> /dev/null; then
        echo "Docker 未安装，正在通过官方脚本安装..."
        if ! curl -sSL https://get.docker.com/ | sh; then
            echo "Docker 安装失败，请手动安装后重试。"
            exit 1
        fi

        local i=0
        while ! command -v docker &> /dev/null; do
            sleep 1
            i=$((i + 1))
            if [ $i -ge 30 ]; then
                echo "错误: Docker 安装后命令仍不可用，请检查环境。"
                exit 1
            fi
        done
        echo "Docker 命令已就绪，正在启动服务..."
    else
        echo "Docker 已安装，正在等待服务就绪..."
    fi

    if command -v systemctl &> /dev/null; then
        sudo systemctl start docker 2>/dev/null
        sudo systemctl enable docker 2>/dev/null
    fi

    wait_for_docker_daemon
    echo "Docker 服务已就绪。"

    local i=0
    while ! docker compose version &> /dev/null && ! command -v docker-compose &> /dev/null; do
        sleep 2
        i=$((i + 1))
        if [ $i -ge 30 ]; then
            echo "错误: 未检测到 docker-compose，请确保 Docker Compose 插件已安装。"
            exit 1
        fi
    done
    echo "Docker Compose 已就绪。"
}

# 随机生成 MySQL 密码（大写、小写、数字、常规符号 @_-#，不含其他特殊字符）
generate_mysql_password() {
    local length="${1:-16}"
    local upper='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    local lower='abcdefghijklmnopqrstuvwxyz'
    local digits='0123456789'
    local symbols='@#_-'
    local all="${upper}${lower}${digits}${symbols}"
    local password=""
    local i

    password+="${upper:$((RANDOM % 26)):1}"
    password+="${lower:$((RANDOM % 26)):1}"
    password+="${digits:$((RANDOM % 10)):1}"
    password+="${symbols:$((RANDOM % ${#symbols})):1}"

    for ((i=4; i<length; i++)); do
        password+="${all:$((RANDOM % ${#all})):1}"
    done

    if command -v shuf &> /dev/null; then
        MYSQL_PASSWORD=$(echo "$password" | fold -w1 | shuf | tr -d '\n')
    else
        MYSQL_PASSWORD=$(echo "$password" | fold -w1 | awk 'BEGIN{srand()} {print rand()"\t"$0}' | sort -n | cut -f2- | tr -d '\n')
    fi
}

# 0. 检查并安装必需命令及 Docker 环境
echo "----------------------------------------------------"
echo "步骤 0: 检查运行环境..."
ensure_command curl
ensure_docker
echo "----------------------------------------------------"

# 交互式菜单选择函数
select_menu() {
    local prompt="$1"
    shift
    local options=("$@")
    local selected=0
    local key

    # 隐藏光标
    echo -ne "\033[?25l"

    # 处理中断（如 Ctrl+C）时恢复光标
    trap 'echo -ne "\033[?25h"; exit' INT TERM

    while true; do
        echo -e "\r$prompt"
        for i in "${!options[@]}"; do
            if [ $i -eq $selected ]; then
                echo -e "  \033[32m>\033[0m \033[7m ${options[$i]} \033[0m"
            else
                echo -e "    ${options[$i]}  "
            fi
        done

        # 读取按键 (单个字符，不回显)
        IFS= read -s -n 1 key

        # 如果是转义序列 (箭头键 \e[A \e[B)
        if [[ "$key" == $'\e' ]]; then
            read -s -n 2 -t 0.1 next_keys
            key="$key$next_keys"
        fi

        case "$key" in
            $'\e[A') # 上箭头
                ((selected--))
                [ $selected -lt 0 ] && selected=$((${#options[@]} - 1))
                ;;
            $'\e[B'|$'\t') # 下箭头 或 Tab
                ((selected++))
                [ $selected -ge ${#options[@]} ] && selected=0
                ;;
            "") # 回车 (Enter)
                break
                ;;
        esac

        # 清除刚才渲染的行 (选项数 + 1行提示)
        local lines=$((${#options[@]} + 1))
        for ((c=1; c<=lines; c++)); do
            echo -ne "\033[F\033[K"
        done
    done

    # 恢复光标并清理 Trap
    echo -ne "\033[?25h"
    trap - INT TERM

    return $selected
}

# 0.5. 选择部署版本
echo "----------------------------------------------------"
options=("pro (默认)" "v9")
select_menu "请选择要部署的版本 (使用 上下键/Tab 切换，回车确认):" "${options[@]}"
BRANCH_CHOICE=$?

if [ $BRANCH_CHOICE -eq 1 ]; then
    DEPLOY_BRANCH="v9"
else
    DEPLOY_BRANCH="pro"
fi
echo "已选择部署版本: $DEPLOY_BRANCH"
EXTRACT_DIR="$DEPLOY_BRANCH"

# 判断是否为 IPv4 地址（部署域名不允许使用 IP）
is_ipv4() {
    local ip="$1"
    [[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
    local o1 o2 o3 o4
    IFS='.' read -r o1 o2 o3 o4 <<< "$ip"
    for o in "$o1" "$o2" "$o3" "$o4"; do
        [[ "$o" -ge 0 && "$o" -le 255 ]] || return 1
    done
    return 0
}

# 2. 交互式输入域名
echo "----------------------------------------------------"
echo "步骤 2: 基础信息配置..."

while true; do
    read -p "请输入部署域名 (例如: www.example.com): " DOMAIN
    if [ -z "$DOMAIN" ]; then
        echo -e "\033[31m[错误] 部署域名不能为空，请重新输入。\033[0m"
    elif is_ipv4 "$DOMAIN"; then
        echo -e "\033[31m[错误] 部署域名不能为 IP 地址，请填写域名。\033[0m"
    else
        break
    fi
done

EMAIL="mail@zanpiancms.com"

CLOUDFLARE_API_TOKEN=""
if [[ "$DOMAIN" == *"*"* ]]; then
    while true; do
        read -p "检测到泛域名，请输入 Cloudflare API Token: " CLOUDFLARE_API_TOKEN
        if [ -z "$CLOUDFLARE_API_TOKEN" ]; then
            echo -e "\033[31m[错误] Cloudflare API Token 不能为空，请重新输入。\033[0m"
        else
            break
        fi
    done
fi

generate_mysql_password
echo "已自动生成 MySQL 密码。"

# 3. 制作 docker-compose.yml 文件并准备环境
echo "----------------------------------------------------"
echo "步骤 3: 生成 docker-compose.yml 并准备环境..."

# 首次部署时将镜像内 /var/www/html 复制到 app_html 共享卷（供 Caddy file_server 回源）
ensure_app_html_volume() {
    local image="zanpiancms/${DEPLOY_BRANCH}"
    local volume_name
    volume_name="$(basename "$(pwd)")_app_html"

    echo "正在检查 app_html 共享卷..."
    docker volume inspect "$volume_name" >/dev/null 2>&1 || docker volume create "$volume_name" >/dev/null

    if ! docker run --rm \
        -v "${volume_name}:/mnt/html" \
        "$image" \
        sh -c 'if [ -z "$(ls -A /mnt/html 2>/dev/null)" ]; then cp -a /var/www/html/. /mnt/html/; fi'; then
        echo "错误: app_html 共享卷初始化失败，请检查 Docker 镜像 zanpiancms/${DEPLOY_BRANCH} 是否可用。"
        exit 1
    fi
}

# 生成 docker-compose.yml
cat > docker-compose.yml <<'EOF'
services:
  app:
    image: zanpiancms/${DEPLOY_BRANCH}
    pull_policy: always
    privileged: true
    container_name: zanpiancms.${DEPLOY_BRANCH}
    volumes:
      - app_html:/var/www/html
      - ./${EXTRACT_DIR}/config.ini:/var/www/html/config.ini
      - ./${EXTRACT_DIR}/upload:/var/www/html/upload
      - ./${EXTRACT_DIR}/logs:/var/app/.logs
    environment:
      MAX_MULTIPART_MEMORY: 200
      TRUSTED_PLATFORM: "CF-Connecting-IP"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network

  mysql:
    image: mysql:5.7.27
    container_name: mysql
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_ALLOW_EMPTY_PASSWORD: false
      TZ: Asia/Shanghai
    command: [ '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ]
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h localhost -uroot -p\"$$MYSQL_ROOT_PASSWORD\" || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 15s
    volumes:
      - ./${EXTRACT_DIR}/mysql/data:/var/lib/mysql
      - ./${EXTRACT_DIR}/mysql/init:/docker-entrypoint-initdb.d
      - ./${EXTRACT_DIR}/mysql/conf.d/my.cnf:/etc/mysql/conf.d/my.cnf:ro
    networks:
      - app-network

  redis:
    image: redis:alpine
    container_name: redis
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
    command: --slave-read-only no
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    volumes:
      - ./${EXTRACT_DIR}/redis:/data
    networks:
      - app-network

  caddy:
    image: zanpiancms/caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - ACME_AGREE=true
      - EMAIL=${EMAIL}
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}
      - EXTRACT_DIR=${EXTRACT_DIR}
      - DOMAIN=${DOMAIN}
    volumes:
      - app_html:/var/www/html
      - ./${EXTRACT_DIR}/upload:/var/www/html/upload:ro
      - ./${EXTRACT_DIR}/caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./${EXTRACT_DIR}/caddy/data:/data
      - ./${EXTRACT_DIR}/caddy/config:/config
    networks:
      - app-network

volumes:
  app_html:

networks:
  app-network:
    driver: bridge
EOF

# 如果 .env 已存在，先备份
if [ -f ".env" ]; then
    cp .env .env.bak
fi

# 写入新的环境变量
cat > .env <<EOF
MYSQL_PASSWORD="$MYSQL_PASSWORD"
EXTRACT_DIR=$EXTRACT_DIR
DEPLOY_BRANCH=$DEPLOY_BRANCH
DOMAIN=$DOMAIN
EMAIL=$EMAIL
CLOUDFLARE_API_TOKEN="$CLOUDFLARE_API_TOKEN"
EOF

# 修改 Caddy 配置文件
CADDY_FILE="${EXTRACT_DIR}/caddy/Caddyfile"
echo "正在根据所选版本 ($DEPLOY_BRANCH) 生成 Caddyfile..."

# 准备 SSL 配置
SSL_CONFIG=""
if [[ "$DOMAIN" == *"*"* ]]; then
    SSL_CONFIG="    tls {
        dns cloudflare {\$CLOUDFLARE_API_TOKEN}
    }"
fi

# 如果 caddy 目录不存在则创建
mkdir -p "${EXTRACT_DIR}/caddy"

# 写入新的 Caddyfile（v9 通过 app_html 共享卷由 Caddy file_server 直接回源静态文件）
if [ "$DEPLOY_BRANCH" = "v9" ]; then
    cat > "$CADDY_FILE" <<EOF
{
    email {\$EMAIL}
}

(hsts) {
    header Strict-Transport-Security "max-age=2592000; includeSubDomains; preload"
}

(blocked-rules) {
    @blockUA expression \`
        header_regexp('User-Agent', '(?i)Scrapy|Curl|HttpClient') ||
        header_regexp('User-Agent', '(?i)python-requests|Go-http-client|WinHttp|WebZIP|FetchURL|node-superagent|java/|FeedDemon|Jullo') ||
        header_regexp('User-Agent', '(?i)Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|Java|Feedly|Apache-HttpAsyncClient|UniversalFeedParser') ||
        header_regexp('User-Agent', '(?i)ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt') ||
        header_regexp('User-Agent', '(?i)MJ12bot|heritrix|EasouSpider|JikeSpider|Ezooms|BOT/0.1|FlightDeckReports|SemrushBot|Linguee Bot')
    \`

    @blocked_ip {
        remote_ip 103.116.123.0/24
    }

    @options {
        method OPTIONS
    }

    respond @blockUA 403
    respond @blocked_ip 200
    respond @options 204
}

(static-files) {
    @static_resources {
        path /theme/* /public/* /upload/* /runtime/*
        not path_regexp \.html$
        file
    }
    handle @static_resources {
        file_server
    }
}

(common-config) {
    encode gzip zstd
    import hsts
    import blocked-rules
    import static-files
}

{\$DOMAIN} {
$SSL_CONFIG
    import common-config
    root * /var/www/html
    php_fastcgi zanpiancms.{\$EXTRACT_DIR}:9000
}
EOF
else
    AUTH_CONFIG="    forward_auth zanpiancms.{\$EXTRACT_DIR}:2026 {
        uri /check-domain
    }"

    BACKEND_CONFIG="    reverse_proxy zanpiancms.{\$EXTRACT_DIR}:2026"

    NOT_IP_HOST_MATCHER="    @not_ip_host {
        not header_regexp Host ^(?:\d{1,3}\.){3}\d{1,3}(:\d+)?\$
    }"

    CATCHALL_AUTH_CONFIG="    forward_auth @not_ip_host zanpiancms.{\$EXTRACT_DIR}:2026 {
        uri /check-domain
    }"

    cat > "$CADDY_FILE" <<EOF
{
    email {\$EMAIL}
    on_demand_tls {
        ask http://zanpiancms.{\$EXTRACT_DIR}:2026/check-domain
    }
}

(hsts) {
    header Strict-Transport-Security "max-age=2592000; includeSubDomains; preload"
}

(blocked-rules) {
    @blockUA expression \`
        header_regexp('User-Agent', '(?i)Scrapy|Curl|HttpClient') ||
        header_regexp('User-Agent', '(?i)python-requests|Go-http-client|WinHttp|WebZIP|FetchURL|node-superagent|java/|FeedDemon|Jullo') ||
        header_regexp('User-Agent', '(?i)Indy Library|Alexa Toolbar|AskTbFXTV|AhrefsBot|CrawlDaddy|Java|Feedly|Apache-HttpAsyncClient|UniversalFeedParser') ||
        header_regexp('User-Agent', '(?i)ApacheBench|Microsoft URL Control|Swiftbot|ZmEu|oBot|jaunty|Python-urllib|lightDeckReports Bot|YYSpider|DigExt') ||
        header_regexp('User-Agent', '(?i)MJ12bot|heritrix|EasouSpider|JikeSpider|Ezooms|BOT/0.1|FlightDeckReports|SemrushBot|Linguee Bot')
    \`

    @blocked_ip {
        remote_ip 103.116.123.0/24
    }

    @options {
        method OPTIONS
    }

    respond @blockUA 403
    respond @blocked_ip 200
    respond @options 204
}

(static-files) {
    @static_resources {
        path /theme/* /public/* /upload/* /runtime/*
        not path_regexp \.html$
        file
    }
    handle @static_resources {
        file_server
    }
}

(common-config) {
    encode gzip zstd
    import hsts
    import blocked-rules
    import static-files
}

{\$DOMAIN} {
$SSL_CONFIG
$AUTH_CONFIG
    import common-config
    root * /var/www/html
$BACKEND_CONFIG
}

http://, https:// {
    tls {
        on_demand
    }

$NOT_IP_HOST_MATCHER
$CATCHALL_AUTH_CONFIG
    import common-config
    root * /var/www/html
$BACKEND_CONFIG
}
EOF
fi


# 检查当前系统支持哪种 compose 命令 (docker compose 或 docker-compose)
DOCKER_COMPOSE_CMD="docker compose"
if ! docker compose version &> /dev/null; then
    DOCKER_COMPOSE_CMD="docker-compose"
fi
echo "正在为目录赋予权限..."
mkdir -p "${EXTRACT_DIR}/mysql/data" "${EXTRACT_DIR}/mysql/conf.d" "${EXTRACT_DIR}/upload" "${EXTRACT_DIR}/theme" "${EXTRACT_DIR}/logs"
cat > "${EXTRACT_DIR}/mysql/conf.d/my.cnf" <<'EOF'
[mysql]
no-auto-rehash
default-character-set=utf8mb4

[mysqld]
skip-name-resolve
# default-time-zone = '+08:00'
default_storage_engine = InnoDB
performance_schema_max_table_instances = 400
table_definition_cache = 400
key_buffer_size = 32M
max_allowed_packet = 1G
table_open_cache = 128
host_cache_size = 0
sort_buffer_size = 768K
net_buffer_length = 4K
read_buffer_size = 768K
read_rnd_buffer_size = 256K
myisam_sort_buffer_size = 8M
thread_cache_size = 16
tmp_table_size = 32M
sql-mode = NO_ZERO_DATE,NO_ZERO_IN_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES

explicit_defaults_for_timestamp = true
max_connections = 500
max_connect_errors = 100
open_files_limit = 65535

log-bin = mysql-bin
# binlog_expire_logs_seconds = 86400
binlog_format = mixed
server-id = 1
slow_query_log = 1
long_query_time = 3
#log_queries_not_using_indexes=on


innodb_data_file_path = ibdata1:10M:autoextend
innodb_buffer_pool_size = 128M
innodb_flush_log_at_trx_commit = 1
innodb_lock_wait_timeout = 50
innodb_max_dirty_pages_pct = 90
innodb_read_io_threads = 2
innodb_write_io_threads = 2

[mysqldump]
quick
max_allowed_packet = 500M

[myisamchk]
key_buffer_size = 32M
sort_buffer_size = 768K
read_buffer = 2M
write_buffer = 2M

[mysqlhotcopy]
interactive-timeout
EOF
: > "${EXTRACT_DIR}/config.ini"
chmod -R 777 "${EXTRACT_DIR}/upload" "${EXTRACT_DIR}/theme" "${EXTRACT_DIR}/config.ini" "${EXTRACT_DIR}/logs"
echo "----------------------------------------------------"

ensure_app_html_volume

echo "正在运行: $DOCKER_COMPOSE_CMD up -d"
if $DOCKER_COMPOSE_CMD up -d; then
    # 4. 如果拉起成功，则输出成功信息，并退出。
    echo "步骤 4: 部署成功！"
    echo "所有服务已成功拉起。"
    echo "您可以访问 http://$DOMAIN 进行安装。"
    echo "MySQL 密码: $MYSQL_PASSWORD"
    echo "----------------------------------------------------"
    exit 0
else
    # 如果拉起失败，则输出错误信息并退出。
    echo "----------------------------------------------------"
    echo "部署失败！"
    echo "发生错误，请查看以上输出信息及 Docker 日志。"
    exit 1
fi
