#!/bin/sh
#
# storage_monitor.sh
# 存储监控守护进程 — systemd 服务
# 在 emsd 之前启动，持续检测存储状态
# 无卡时自动降级(tmpfs)，检测到 eMMC 写入危险时告警
#
# 判断逻辑:
#   ■ 无卡 + emsd运行但proto_forward没运行 → 程序因database打不开反复崩溃 → 执行降级
#   ■ 无卡 + emsd和proto_forward都没运行 + 系统已启动一阵 → 确实无卡 → 执行降级
#   ■ 无卡 + proto_forward在运行 + database是普通目录 → 数据写入eMMC! → 告警不操作
#   ■ 有卡 → 正常，不操作
#
# 适用: 所有 WLOSANY 系列板型
# 用法: 由 systemd 管理
#       systemctl start/stop/status storage-monitor
#

# ============================== 配置 ==============================
CHECK_INTERVAL=30              # 监控检测间隔(秒)
TMPFS_SIZE=100                 # 降级 tmpfs 大小(MB)
MIN_UPTIME_SAFE=5              # 无块设备时的最小等待(秒)，小于此值等 kernel 枚举设备
MIN_UPTIME_RETRY=15             # 有块设备但挂载失败时的最小等待(秒)，等别的服务挂载
LOG_FILE="/tmp/storage_monitor.log"
LOG_MAX_LINES=1000             # 日志最大行数(超出后截断)
PID_FILE="/var/run/storage_monitor.pid"
# ==================================================================

# 全局变量
BOARD=""
PRODUCT=""
SN=""
SD_PATH=""
SD_NAME=""
LINK_DB="/opt/lnxall_app/app/database"
DIR_DB_SD=""
BACKUP_DIR="/opt/lnxall_app/app"
BACKUP_NAME="database.emmc_backup"
DEGRADE_APPLIED=0              # 0=未执行过降级  1=已执行过降级
BLOCK_DEV_EXISTS=0             # 0=没找到任何块设备  1=找到了但挂载失败
LOOP_COUNT=0

# ==================================================================
# 日志函数 (同时输出到终端和日志文件)
# ==================================================================
log()     { local m="[$(date '+%m-%d %H:%M:%S')] $*"; echo "$m" | tee -a "$LOG_FILE"; }
log_i()   { log "INFO   $*"; }
log_w()   { log "WARN   $*"; }
log_e()   { log "ERROR  $*"; }
log_ok()  { log "OK     $*"; }

# 日志截断(超出最大行数时保留后半)
rotate_log() {
    local lines=$(wc -l < "$LOG_FILE" 2>/dev/null || echo 0)
    if [ "$lines" -gt "$LOG_MAX_LINES" ]; then
        tail -n $((LOG_MAX_LINES / 2)) "$LOG_FILE" > "${LOG_FILE}.tmp"
        mv "${LOG_FILE}.tmp" "$LOG_FILE"
    fi
}

# ==================================================================
# 单实例保护
# ==================================================================
ensure_single_instance() {
    if [ -f "$PID_FILE" ]; then
        local old_pid=$(cat "$PID_FILE" 2>/dev/null)
        if [ -n "$old_pid" ] && kill -0 "$old_pid" 2>/dev/null; then
            echo "storage_monitor 已在运行 (PID=$old_pid)"
            exit 1
        fi
    fi
    echo $$ > "$PID_FILE"
}

cleanup() {
    local rc=$?
    rm -f "$PID_FILE"
    log "storage_monitor 退出 (PID=$$, exit=$rc)"
    exit $rc
}

# ==================================================================
# 设备类型检测 (只执行一次，板型不会变)
# ==================================================================
detect_device() {
    BOARD=$(cat /tmp/sysinfo/board_name 2>/dev/null || echo "")
    if [ -z "$BOARD" ]; then
        log_e "无法读取 board_name (/tmp/sysinfo/board_name)"
        return 1
    fi

    SN=$(factory get 2>/dev/null | awk -F= '/^SN=/{print $2}')
    [ -z "$SN" ] && SN="unknown"

    case "$BOARD" in
        WLOSANY_RK3568|WLOSANY_RK3568_XDDL|WLOSANY_RK3568_FORLINX|WLOSANY_BAU-B30-003)
            SD_PATH="/mnt/ssd"
            SD_NAME="SSD"
            return 0
            ;;
        WLOSANY_LC100)
            PRODUCT=$(factory get 2>/dev/null | awk -F= '/^PRODUCT=/{print $2}')
            if [ -z "$PRODUCT" ]; then
                log_e "无法读取 PRODUCT (factory get)"
                return 1
            fi
            case "$PRODUCT" in
                *500*)
                    SD_PATH="/mnt/sdisk"
                    SD_NAME="TF卡(sdisk)"
                    return 0
                    ;;
                *)
                    log_e "LC100 板型, PRODUCT=$PRODUCT 不含500, 未知版型"
                    return 1
                    ;;
            esac
            ;;
        *)
            log_e "未知板型: $BOARD"
            return 1
            ;;
    esac
}

# ==================================================================
# 检查存储卡状态
#   返回值: 0=有卡(真实设备已挂载)  1=无卡  2=降级环境(tmpfs)
#   同时设置 BLOCK_DEV_EXISTS: 0=没找到块设备  1=找到了但挂载失败
# ==================================================================
check_card() {
    BLOCK_DEV_EXISTS=0

    # 检查是否已有 tmpfs 降级挂载
    if mount 2>/dev/null | grep -q "$SD_PATH.*tmpfs"; then
        return 2
    fi

    # 检查是否有真实设备挂载
    if mount 2>/dev/null | grep -q "$SD_PATH"; then
        return 0
    fi

    # 都没挂载 → 尝试找块设备并自动挂载
    local DEV=""
    if [ "$SD_PATH" = "/mnt/ssd" ]; then
        for d in nvme0n1 sda; do
            if [ -b "/dev/$d" ]; then
                DEV="/dev/$d"
                BLOCK_DEV_EXISTS=1
                break
            fi
        done
    else
        DEV=$(ls /dev/mmcblk?p? 2>/dev/null | head -1)
        if [ -n "$DEV" ]; then
            BLOCK_DEV_EXISTS=1
        else
            DEV=$(ls /dev/mmcblk? 2>/dev/null | head -1)
            [ -n "$DEV" ] && BLOCK_DEV_EXISTS=1
        fi
    fi

    if [ -n "$DEV" ] && [ -b "$DEV" ]; then
        [ -L "$SD_PATH" ] && rm -f "$SD_PATH"
        mkdir -p "$SD_PATH"
        local FSTYPE=$(timeout 5 blkid -s TYPE -o value "$DEV" 2>/dev/null)
        if timeout 10 mount "$DEV" "$SD_PATH" 2>/dev/null || \
           ([ -n "$FSTYPE" ] && timeout 10 mount -t "$FSTYPE" "$DEV" "$SD_PATH" 2>/dev/null); then
            return 0
        fi
        umount "$SD_PATH" 2>/dev/null || true
        rmdir "$SD_PATH" 2>/dev/null || true
    fi

    return 1
}

# ==================================================================
# 检查服务状态
#   EMSD_RUNNING:  emsd 服务是否 active
#   PF_RUNNING:    proto_forward 进程是否存在
#   EMSD_DEGRADED: emsd 运行中但 proto_forward 反复崩溃
# ==================================================================
EMSD_RUNNING=0
PF_RUNNING=0
EMSD_DEGRADED=0

check_services() {
    EMSD_RUNNING=0
    PF_RUNNING=0
    EMSD_DEGRADED=0

    # 检查 emsd 服务状态
    systemctl is-active --quiet emsd 2>/dev/null && EMSD_RUNNING=1

    # 检查 proto_forward 进程
    pidof proto_forward >/dev/null 2>&1 && PF_RUNNING=1

    # emsd 运行中 但 proto_forward 不在 → 程序反复崩溃(通常是database打不开)
    if [ "$EMSD_RUNNING" -eq 1 ] && [ "$PF_RUNNING" -eq 0 ]; then
        EMSD_DEGRADED=1
    fi
}

# ==================================================================
# 检查 database 类型
#   返回值: 0=软链接  1=普通目录  2=不存在
# ==================================================================
check_database_type() {
    if [ -L "$LINK_DB" ]; then
        return 0
    elif [ -d "$LINK_DB" ]; then
        return 1
    else
        return 2
    fi
}

# 获取 database 描述字符串
get_db_desc() {
    check_database_type
    local t=$?
    case "$t" in
        0) echo "软链接→$(readlink "$LINK_DB" 2>/dev/null)" ;;
        1)
            local n=$(find "$LINK_DB" -type f 2>/dev/null | wc -l)
            local sz=$(du -sh "$LINK_DB" 2>/dev/null | awk '{print $1}')
            echo "普通目录(${n}文件,${sz})"
            ;;
        2) echo "不存在" ;;
    esac
}

# ==================================================================
# 获取系统运行时间(秒)
# ==================================================================
get_uptime() {
    awk '{print int($1)}' /proc/uptime 2>/dev/null || echo 0
}

# ==================================================================
# 备份 eMMC 数据 (仅在 database 为普通目录时执行)
# ==================================================================
backup_emmc_data() {
    local BAK_PATH="${BACKUP_DIR}/${BACKUP_NAME}"

    check_database_type
    local db_type=$?
    if [ "$db_type" -ne 1 ]; then
        [ "$db_type" -eq 2 ] && log_i "database 不存在，无需备份"
        [ "$db_type" -eq 0 ] && log_i "database 已是软链接，无需备份"
        return 0
    fi

    local n=$(find "$LINK_DB" -type f 2>/dev/null | wc -l)
    if [ "$n" -eq 0 ]; then
        log_i "database 目录为空，直接删除"
        rm -rf "$LINK_DB"
        return 0
    fi

    local data_size=$(du -sm "$LINK_DB" 2>/dev/null | awk '{print $1}')
    log_i "database 数据量: ${data_size}M ($n 个文件)"

    # 检查备份是否已存在
    if [ -e "$BAK_PATH" ]; then
        log_w "备份目录已存在，覆盖"
        rm -rf "$BAK_PATH"
    fi

    # 检查 eMMC 空间
    local avail=$(df -m "${BACKUP_DIR}" 2>/dev/null | tail -1 | awk '{print $4}')
    local need=$((data_size + 100))
    if [ -z "$avail" ] || [ "$avail" -lt "$need" ]; then
        log_e "eMMC 空间不足! 需要 ${need}M, 剩余 ${avail}M"
        return 1
    fi

    log_i "开始备份: $LINK_DB → $BAK_PATH"
    local t0=$(date +%s)
    if ! cp -a "$LINK_DB" "$BAK_PATH" 2>/dev/null; then
        log_e "备份失败!"
        return 1
    fi
    local t1=$(date +%s)

    local bak_n=$(find "$BAK_PATH" -type f 2>/dev/null | wc -l)
    local bak_size=$(du -sm "$BAK_PATH" 2>/dev/null | awk '{print $1}')
    log_ok "备份完成 ($bak_n 文件, ${bak_size}M, $((t1-t0))s)"

    # 删除 eMMC 上的原目录
    rm -rf "$LINK_DB"
    log_i "已删除 eMMC 上原 database 目录"
    return 0
}

# ==================================================================
# 执行降级: 挂载 tmpfs + 建目录 + 建软链接
# ==================================================================
setup_tmpfs() {
    log_i "挂载 tmpfs: $SD_PATH (size=${TMPFS_SIZE}M，内存)"

    # 处理残留
    [ -L "$LINK_DB" ] && rm -f "$LINK_DB"
    [ -L "$SD_PATH" ] && rm -f "$SD_PATH"
    mkdir -p "$SD_PATH"
    umount "$SD_PATH" 2>/dev/null || true

    # 挂载 tmpfs
    if ! mount -t tmpfs -o size=${TMPFS_SIZE}M,nosuid,nodev,noexec tmpfs "$SD_PATH" 2>/dev/null; then
        log_e "tmpfs 挂载失败!"
        return 1
    fi
    log_ok "tmpfs 已挂载 (size=${TMPFS_SIZE}M)"

    # 创建目录结构
    mkdir -p "$DIR_DB_SD"
    mkdir -p "$SD_PATH/history"
    log_i "目录结构已创建"

    # 建软链接
    ln -svf "$DIR_DB_SD" "$LINK_DB" 2>&1 | tee -a "$LOG_FILE"
    log_ok "软链接: $LINK_DB → $DIR_DB_SD"

    # TF卡板型: 建立 /mnt/ssd → /mnt/sdisk
    if [ "$SD_PATH" = "/mnt/sdisk" ]; then
        [ -L "/mnt/ssd" ] && rm -f "/mnt/ssd"
        [ -d "/mnt/ssd" ] && [ "$(find /mnt/ssd -maxdepth 1 2>/dev/null | wc -l)" -le 1 ] && rm -rf "/mnt/ssd"
        if [ ! -e "/mnt/ssd" ]; then
            ln -svf /mnt/sdisk /mnt/ssd 2>&1 | tee -a "$LOG_FILE"
        fi
    fi

    return 0
}

# ==================================================================
# 降级入口 (仅在安全时调用)
# ==================================================================
apply_degradation() {
    log_w ""
    log_w "╔══════════════════════════════════════════════════════════╗"
    log_w "║  执行降级: 无卡 → tmpfs ${TMPFS_SIZE}M 内存假存储             ║"
    log_w "╚══════════════════════════════════════════════════════════╝"
    log_w ""

    backup_emmc_data || {
        log_e "备份失败，终止降级以保证数据安全"
        return 1
    }

    setup_tmpfs || {
        log_e "tmpfs 创建失败"
        return 1
    }

    DEGRADE_APPLIED=1
    log_ok ""
    log_ok "【降级成功】程序可正常启动，写入限制 ${TMPFS_SIZE}M"
    log_ok "  数据在内存中，重启后丢失，需重新执行本脚本"
    log_ok ""
    return 0
}

# ==================================================================
# 告警: 检测到数据正在写入 eMMC
# ==================================================================
alert_emmc_danger() {
    local db_desc="$1"
    log_e ""
    log_e "╔══════════════════════════════════════════════════════════╗"
    log_e "║  ⚠️  危险！数据正在写入 eMMC！                           ║"
    log_e "║  卡不在位，但 /app/database 是普通目录                 ║"
    log_e "║  数据持续写入将撑爆 eMMC                                ║"
    log_e "║  当前状态: $db_desc"
    log_e "║                                                        ║"
    log_e "║  处理建议:                                              ║"
    log_e "║    1. 停止程序: killall proto_forward                   ║"
    log_e "║    2. 本服务将自动执行降级                               ║"
    log_e "║    3. 程序恢复后可正常运行                               ║"
    log_e "╚══════════════════════════════════════════════════════════╝"
    log_e ""
}

# ==================================================================
# 单次检测 + 决策
# ==================================================================
do_check() {
    LOOP_COUNT=$((LOOP_COUNT + 1))

    check_services
    check_card
    local CARD=$?

    check_database_type
    local DB_TYPE=$?
    local DB_DESC=$(get_db_desc)

    local UPTIME=$(get_uptime)

    # ---- 状态摘要 ----
    local CARD_STR="无卡"
    [ "$CARD" -eq 0 ] && CARD_STR="有卡(已挂载)"
    [ "$CARD" -eq 2 ] && CARD_STR="降级(tmpfs)"

    local SVC_STR=""
    [ "$EMSD_RUNNING" -eq 1 ] && SVC_STR="${SVC_STR}emsd(active) "
    [ "$PF_RUNNING" -eq 1 ] && SVC_STR="${SVC_STR}proto_forward(PID=$(pidof proto_forward)) "
    [ "$EMSD_DEGRADED" -eq 1 ] && SVC_STR="${SVC_STR}[emsd运行中但proto_forward反复崩溃!] "
    [ -z "$SVC_STR" ] && SVC_STR="无服务运行"

    log_i "[#$LOOP_COUNT] uptime=${UPTIME}s | 卡=$CARD_STR | 服务=$SVC_STR | database=$DB_DESC"

    # ================================================================
    # 决策逻辑
    # ================================================================

    case "$CARD" in
        0)
            # ---- 有卡(真实设备已挂载) ----
            if [ "$DB_TYPE" -eq 1 ]; then
                # 有卡但是 database 是普通目录 → 卡白插了，数据在 eMMC！
                if [ "$PF_RUNNING" -eq 1 ]; then
                    # 程序在运行 → 不能动
                    log_w ""
                    log_w "  ╔══════════════════════════════════════════════════╗"
                    log_w "  ║  ⚠️  卡已挂载但 database 是普通目录(数据在 eMMC) ║"
                    log_w "  ║  卡空间被浪费，eMMC 持续被写入                ║"
                    log_w "  ║  建议: 运行 fix_database_to_sd.sh 迁移数据    ║"
                    log_w "  ╚══════════════════════════════════════════════════╝"
                    log_w ""
                else
                    # 程序没运行 → 删掉 eMMC 目录
                    # create_database_dir 会在启动时重建软链接指向卡
                    local n=$(find "$LINK_DB" -type f 2>/dev/null | wc -l)
                    local sz=$(du -sh "$LINK_DB" 2>/dev/null | awk '{print $1}')
                    log_w "  有卡 + database是普通目录 + 程序未运行"
                    log_w "  → 删除 eMMC 目录(原${n}文件,${sz})，create_database_dir 将重建软链接指向卡"
                    rm -rf "$LINK_DB"
                    log_ok "  已删除，程序下次启动时自动链接到卡"
                fi
            fi
            # 有卡时一切正常，不做任何操作
            ;;

        2)
            # ---- 降级环境(tmpfs 已挂载) ----
            if [ "$DB_TYPE" -ne 0 ]; then
                log_w "  tmpfs 已挂载但软链接异常($DB_DESC)，需检查"
            fi
            # 降级环境正常，不做操作
            ;;

        1)
            # ---- 无卡(无挂载) ----
            #
            # 分两条路径:
            #   路径A: database 是普通目录 → 数据在 eMMC! → 立即删除(停血)
            #          (不等待，不等 uptime，删除 eMMC 目录不依赖卡)
            #   路径B: database 是软链接(目标丢失) 或 不存在
            #          → 等系统稳定后执行降级(挂 tmpfs + 建软链接)
            #
            # 降级等待分两档:
            #   - 没找到任何块设备: 5s  (kernel扫设备很快)
            #   - 有块设备但挂载失败: 15s (可能是别的服务在挂载)

            if [ "$DB_TYPE" -eq 1 ]; then
                # ===== 路径A: 普通目录 = eMMC 上有数据 = 紧急！=====
                if [ "$PF_RUNNING" -eq 1 ]; then
                    # 程序在运行 → 不能动！
                    alert_emmc_danger "$DB_DESC"
                else
                    # 程序没运行 → 直接删，不等！
                    local n=$(find "$LINK_DB" -type f 2>/dev/null | wc -l)
                    local sz=$(du -sh "$LINK_DB" 2>/dev/null | awk '{print $1}')
                    log_w "  无卡 + database是普通目录 + 程序未运行"
                    log_w "  → 立即删除 eMMC 目录(原${n}文件,${sz})，停止 eMMC 写入"
                    rm -rf "$LINK_DB"
                    log_ok "  已删除，下一轮检测将自动执行降级"
                fi

            else
                # ===== 路径B: 软链接(目标丢失) 或 不存在 → 降级 =====
                if [ "$PF_RUNNING" -eq 1 ]; then
                    # 程序在运行，不动
                    :
                else
                    local WAIT_TIME=$MIN_UPTIME_SAFE
                    [ "$BLOCK_DEV_EXISTS" -eq 1 ] && WAIT_TIME=$MIN_UPTIME_RETRY

                    if [ "$UPTIME" -lt "$WAIT_TIME" ]; then
                        local reason="等待kernel枚举块设备"
                        [ "$BLOCK_DEV_EXISTS" -eq 1 ] && reason="有块设备但挂载失败，等别的服务"
                        log_i "  系统刚启动${UPTIME}s($reason)，${WAIT_TIME}s后再降级"
                    else
                        # ---- proto_forward 没运行 + emsd 运行 + emud 没运行 → 重启 emsd ----
                        local EMUD_RUNNING=0
                        systemctl is-active --quiet emud 2>/dev/null && EMUD_RUNNING=1
                        if [ "$PF_RUNNING" -eq 0 ] && [ "$EMSD_RUNNING" -eq 1 ] && [ "$EMUD_RUNNING" -eq 0 ]; then
                            log_w "  proto_forward未运行 + emsd运行中 + emud未运行 → 重启emsd尝试恢复"
                            systemctl restart emsd 2>/dev/null && log_ok "  emsd 已重启" || log_w "  emsd 重启失败"
                            sleep 3
                            # 重启后重新检查服务状态
                            check_services
                        fi

                        local reason_str="软链接目标不存在"
                        [ "$DB_TYPE" -eq 2 ] && reason_str="database不存在"
                        log_w "  无卡 + ${reason_str} + 服务未运行(系统已运行${UPTIME}s) → 执行降级"
                        apply_degradation || log_e "降级失败!"
                    fi
                fi
            fi
            ;;
    esac
}

# ==================================================================
# 主入口
# ==================================================================
main() {
    ensure_single_instance
    trap cleanup INT TERM EXIT

    log "═══════════════════════════════════════════════════════════"
    log "storage_monitor 守护进程启动 (PID=$$)"
    log "═══════════════════════════════════════════════════════════"
    log_i "检测间隔: ${CHECK_INTERVAL}s | tmpfs: ${TMPFS_SIZE}M"
    log_i "等待阈值: 无块设备=${MIN_UPTIME_SAFE}s  有块设备但挂载失败=${MIN_UPTIME_RETRY}s"
    log_i "日志: $LOG_FILE"

    # 设备检测 (只做一次)
    if ! detect_device; then
        log_e "设备检测失败，无法继续监控"
        log_e "板型: $BOARD  PRODUCT: $PRODUCT"
        log "监控进程退出"
        exit 1
    fi
    DIR_DB_SD="${SD_PATH}/database"
    log_i "设备: SN=$SN  BOARD=$BOARD  PRODUCT=$PRODUCT"
    log_i "存储: $SD_NAME → $SD_PATH"
    log_i "database 目标: $DIR_DB_SD"
    log ""

    # 主循环
    while true; do
        rotate_log
        do_check
        sleep "$CHECK_INTERVAL"
    done
}

main

