#!/bin/sh
#
# ensure_storage_dir.sh
# 用途: 手动执行，仅在"无卡"时营造降级环境
#       使用 eMMC loop 挂载，不占用内存，写满返回 ENOSPC
#       每次重建镜像确保全新，避免重启后 SQLite mmap 导致 SIGBUS
# 适用: 所有 WLOSANY 系列板型
# 用法: sh ensure_storage_dir.sh
#
# 判断规则:
#   board_name = WLOSANY_RK3568              → SSD   (/mnt/ssd)
#   board_name = WLOSANY_LC100 + PRODUCT含500 → TF卡 (/mnt/sdisk)
#   其他                                      → 未知版型，退出
#

# 全局变量
BOARD=""
PRODUCT=""
SD_PATH=""
SD_NAME=""
LINK_DB="/opt/lnxall_app/app/database"
DIR_DB_SD=""
BACKUP_DIR="/opt/lnxall_app/app"        # 备份存放目录 (eMMC 上)
BACKUP_NAME="database.emmc_backup"      # 备份目录名
LOOP_IMG="/opt/lnxall_app/storage.img"  # eMMC 上的 loop 镜像文件
LOOP_SIZE=20                              # 镜像大小 (MB)

# ==================================================================
# 日志函数
# ==================================================================
log()     { echo "[$(date '+%m-%d %H:%M:%S')] $*"; }
log_i()   { log "[INFO]   $*"; }
log_w()   { log "[WARN]   $*"; }
log_e()   { log "[ERROR]  $*"; }
log_ok()  { log "[OK]     $*"; }
log_hdr() { echo ""; log "============================================================"; log "$*"; }

# ==================================================================
# 步骤1: 识别设备类型
# ==================================================================
detect_device() {
    log_hdr "步骤1: 识别设备类型"

    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
    log_i "板型: $BOARD"

    case "$BOARD" in
        WLOSANY_RK3568)
            SD_PATH="/mnt/ssd"
            SD_NAME="SSD"
            log_i "存储类型: SSD (RK3568)"
            ;;

        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
            log_i "PRODUCT: $PRODUCT"

            case "$PRODUCT" in
                *500*)
                    SD_PATH="/mnt/sdisk"
                    SD_NAME="TF卡(sdisk)"
                    log_i "存储类型: TF卡 (PRODUCT含500)"
                    ;;
                *)
                    log_e "LC100 板型但 PRODUCT=$PRODUCT 不含 500，未知版型"
                    return 1
                    ;;
            esac
            ;;

        *)
            log_e "未知板型: $BOARD"
            return 1
            ;;
    esac

    DIR_DB_SD="${SD_PATH}/database"
    log_i "挂载路径: $SD_PATH"
    log_i "database 目标: $DIR_DB_SD"
}

# ==================================================================
# 步骤2: 检查卡状态
# ==================================================================
check_card_status() {
    log_hdr "步骤2: 检查 $SD_NAME 状态"

    if mount | grep -q "$SD_PATH"; then
        if mount | grep "$SD_PATH" | grep -q "tmpfs\|loop"; then
            log_w "$SD_PATH 已挂载降级环境 (tmpfs/loop)"
            return 1
        fi
        log_ok "$SD_NAME 已挂载 ($SD_PATH)"
        return 0
    fi

    log_w "$SD_NAME 未挂载，尝试自动挂载..."

    local DEV=""
    if [ "$SD_PATH" = "/mnt/ssd" ]; then
        for d in nvme0n1 sda; do
            [ -b "/dev/$d" ] && DEV="/dev/$d" && break
        done
    else
        DEV=$(ls /dev/mmcblk?p? 2>/dev/null | head -1)
        [ -z "$DEV" ] && DEV=$(ls /dev/mmcblk? 2>/dev/null | head -1)
    fi

    if [ -z "$DEV" ] || [ ! -b "$DEV" ]; then
        log_w "未找到 $SD_NAME 块设备"
        return 1
    fi

    log_i "找到块设备: $DEV"

    [ -L "$SD_PATH" ] && rm -f "$SD_PATH"
    mkdir -p "$SD_PATH"

    local FSTYPE=$(blkid -s TYPE -o value "$DEV" 2>/dev/null)
    if mount "$DEV" "$SD_PATH" 2>/dev/null || \
       ([ -n "$FSTYPE" ] && mount -t "$FSTYPE" "$DEV" "$SD_PATH" 2>/dev/null); then
        log_ok "$SD_NAME 自动挂载成功: $DEV → $SD_PATH"
        return 0
    fi

    umount "$SD_PATH" 2>/dev/null || true
    rmdir "$SD_PATH" 2>/dev/null || true
    log_w "$DEV 挂载失败"
    return 1
}

# ==================================================================
# 步骤3: 诊断 database 状态
# ==================================================================
diagnose_database() {
    log_hdr "步骤3: 诊断 database 状态"

    if mount | grep "$SD_PATH" | grep -qE "tmpfs|loop"; then
        log_i "$SD_PATH 已挂载降级环境 (tmpfs/loop)"

        if [ -L "$LINK_DB" ]; then
            local tgt=$(readlink -f "$LINK_DB" 2>/dev/null || readlink "$LINK_DB")
            if [ "$tgt" = "$DIR_DB_SD" ]; then
                log_ok "降级环境已就绪，软链接正确"
                return 0
            else
                log_w "降级环境已挂载但软链接指向错误，需修正"
                return 1
            fi
        else
            log_w "降级环境已挂载但软链接缺失，需修正"
            return 1
        fi
    fi

    if [ -L "$LINK_DB" ]; then
        local tgt=$(readlink -f "$LINK_DB" 2>/dev/null || readlink "$LINK_DB")
        log_i "database: 软链接 → $tgt"
        if [ "$tgt" = "$DIR_DB_SD" ]; then
            if mount | grep -q "$SD_PATH"; then
                if [ -d "$DIR_DB_SD" ]; then
                    log_ok "  指向正确且目标目录存在($SD_NAME 已挂载)"
                    return 0
                else
                    log_w "  指向正确但目标目录不存在"
                    log_w "  需降级处理"
                    return 1
                fi
            else
                log_w "  指向正确但 $SD_PATH 未挂载(数据正在写入 eMMC!)"
                log_w "  需降级处理"
                return 1
            fi
        else
            log_w "  指向错误 (应为 $DIR_DB_SD)，需降级处理"
            return 1
        fi
    elif [ -d "$LINK_DB" ]; then
        local n=$(find "$LINK_DB" -type f 2>/dev/null | wc -l)
        local size=$(du -sh "$LINK_DB" 2>/dev/null | awk '{print $1}')
        log_w "database: 普通目录 ($n 个文件，$size，数据在 eMMC)"
        log_w "  需降级处理，将先备份 eMMC 数据"
        return 1
    else
        log_w "database: 不存在，需降级处理"
        return 1
    fi
}

# ==================================================================
# 步骤4: 备份 eMMC 上的数据
# ==================================================================
backup_emmc_data() {
    log_hdr "步骤4: 备份 eMMC 数据"

    local BAK_PATH="${BACKUP_DIR}/${BACKUP_NAME}"

    if [ ! -d "$LINK_DB" ] || [ -L "$LINK_DB" ]; then
        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
        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_w "备份目录已存在: $BAK_PATH ($bak_n 个文件, ${bak_size}M)"
        if [ "$bak_n" -gt 0 ]; then
            log_w "  发现已有备份，将覆盖"
            rm -rf "$BAK_PATH"
        fi
    fi

    local avail=$(df -m "${BACKUP_DIR}" 2>/dev/null | tail -1 | awk '{print $4}')
    log_i "eMMC 剩余空间: ${avail}M"
    if [ -z "$avail" ]; then
        log_e "无法获取 eMMC 剩余空间，终止备份"
        return 1
    fi

    local need=$((data_size + 100))
    if [ "$avail" -lt "$need" ]; then
        log_e "eMMC 空间不足! 需要 ${need}M，剩余 ${avail}M"
        log_e "请先手动清理 eMMC 空间或删除旧备份"
        return 1
    fi
    log_ok "空间检查通过 (需要 ${need}M，剩余 ${avail}M)"

    log_i "开始备份: $LINK_DB → $BAK_PATH"
    local start_time=$(date +%s)

    if ! cp -a "$LINK_DB" "$BAK_PATH" 2>/dev/null; then
        log_e "备份失败!"
        return 1
    fi

    local end_time=$(date +%s)
    local elapsed=$((end_time - start_time))

    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}')
    if [ "$bak_n" -ne "$n" ]; then
        log_w "备份文件数不一致 (源:$n → 备份:$bak_n)"
        log_w "可能是 journal 等临时文件，继续处理"
    else
        log_ok "备份验证通过 ($bak_n 个文件, ${bak_size}M, 耗时 ${elapsed}s)"
    fi

    log_i "备份路径: $BAK_PATH"

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

    log_w ""
    log_w "  数据已备份，恢复方法:"
    log_w "    1. 插入 $SD_NAME 后运行 ensure_storage_dir.sh 挂载真实卡"
    log_w "    2. 运行 fix_database_to_sd.sh 迁移备份数据到卡上"
    log_w "    3. 或手动: cp -a $BAK_PATH/* \$SD_PATH/database/"
    log_w ""
    return 0
}

# ==================================================================
# 步骤5: 降级模式 — eMMC loop 挂载 (固定大小限制)
#   ★ 每次删除旧镜像，全新 dd + mkfs，与"第一次"条件完全一致
# ==================================================================
setup_loop_degrade() {
    log_hdr "步骤5: 执行降级 (eMMC loop 挂载)"
    log_w ""
    log_w "=========================================="
    log_w "  降级模式: 无 $SD_NAME"
    log_w "  使用 eMMC loop 挂载营造假目录环境"
    log_w "  镜像大小: ${LOOP_SIZE}M (固定限制)"
    log_w "  每次重建镜像，确保全新无残留"
    log_w "  不占用内存，写满返回 ENOSPC"
    log_w "=========================================="

    # ---- 5.1 检查 loop 设备支持 ----
    if [ ! -e /dev/loop0 ] && [ ! -e /dev/loop-control ]; then
        modprobe loop 2>/dev/null || true
    fi

    if ! ls /dev/loop* 2>/dev/null | grep -q loop; then
        log_e "系统不支持 loop 设备!"
        log_e "回退到 tmpfs 方案..."
        setup_tmpfs_fallback
        return $?
    fi
    log_ok "loop 设备支持正常"

    if ! which mkfs.ext4 >/dev/null 2>&1; then
        log_e "mkfs.ext4 不可用!"
        log_e "回退到 tmpfs 方案..."
        setup_tmpfs_fallback
        return $?
    fi

    # ---- 5.2 处理残留的软链接 ----
    if [ -L "$LINK_DB" ]; then
        local tgt=$(readlink -f "$LINK_DB" 2>/dev/null || readlink "$LINK_DB")
        rm -f "$LINK_DB"
        log_i "已删除旧软链接: $LINK_DB (原指向 $tgt，待重建)"
    fi

    # ---- 5.3 准备挂载点 ----
    if [ -d "$SD_PATH" ] && ! [ -L "$SD_PATH" ] && ! mount | grep -q "$SD_PATH"; then
        local sd_n=$(find "$SD_PATH" -maxdepth 1 2>/dev/null | wc -l)
        if [ "$sd_n" -gt 1 ]; then
            log_w "$SD_PATH 下有残留数据($((sd_n-1)) 项，在 eMMC 上)"
            log_w "loop 挂载后将隐藏这些数据(umount 后可恢复)"
        fi
    fi

    [ -L "$SD_PATH" ] && rm -f "$SD_PATH"
    mkdir -p "$SD_PATH"
    umount "$SD_PATH" 2>/dev/null || true

    # ---- 5.4 全新创建镜像 (每次都 rm + dd + mkfs) ----
    if [ -f "$LOOP_IMG" ]; then
        log_i "删除旧镜像: $LOOP_IMG"

        # 卸载已有的 loop 挂载
        local loop_dev=$(losetup -j "$LOOP_IMG" 2>/dev/null | head -1 | cut -d: -f1)
        if [ -n "$loop_dev" ]; then
            umount "$loop_dev" 2>/dev/null || true
            losetup -d "$loop_dev" 2>/dev/null || true
            log_i "已卸载旧 loop 设备: $loop_dev"
        fi

        rm -f "$LOOP_IMG"
    fi

    # ★ 全新创建镜像文件
    log_i "创建镜像文件: $LOOP_IMG (${LOOP_SIZE}M)"

    local avail=$(df -m /opt/lnxall_app 2>/dev/null | tail -1 | awk '{print $4}')
    if [ -z "$avail" ] || [ "$avail" -lt "$((LOOP_SIZE + 10))" ]; then
        log_e "eMMC 空间不足，无法创建镜像 (需要 ${LOOP_SIZE}M)"
        return 1
    fi

    if ! dd if=/dev/zero of="$LOOP_IMG" bs=1M count="$LOOP_SIZE" 2>/dev/null; then
        log_e "创建镜像文件失败"
        return 1
    fi
    log_ok "镜像文件已创建: $LOOP_IMG (${LOOP_SIZE}M)"

    log_i "格式化镜像为 ext4..."
    if ! mkfs.ext4 -F "$LOOP_IMG" >/dev/null 2>&1; then
        log_e "格式化失败"
        rm -f "$LOOP_IMG"
        return 1
    fi
    log_ok "格式化完成 (ext4)"

    # ---- 5.5 loop 挂载 ----
    if ! mount -o loop "$LOOP_IMG" "$SD_PATH" 2>/dev/null; then
        log_e "loop 挂载失败!"
        return 1
    fi
    log_ok "loop 挂载成功: $LOOP_IMG → $SD_PATH"

    # ---- 5.6 创建目录结构 ----
    mkdir -p "$DIR_DB_SD"
    log_i "已创建目录: $DIR_DB_SD"

    mkdir -p "$SD_PATH/history"
    log_i "已创建目录: $SD_PATH/history"

    # ---- 5.7 建立软链接 ----
    if [ ! -e "$LINK_DB" ]; then
        ln -svf "$DIR_DB_SD" "$LINK_DB"
        log_ok "软链接: $LINK_DB -> $DIR_DB_SD"
    fi

    # TF卡板型: 建立 /mnt/ssd -> /mnt/sdisk 软链接
    if [ "$SD_PATH" = "/mnt/sdisk" ]; then
        if [ ! -e "/mnt/ssd" ]; then
            ln -svf /mnt/sdisk /mnt/ssd
            log_ok "软链接: /mnt/ssd -> /mnt/sdisk"
        elif [ -L "/mnt/ssd" ]; then
            local tgt=$(readlink "/mnt/ssd" 2>/dev/null)
            if [ "$tgt" != "/mnt/sdisk" ]; then
                rm -f "/mnt/ssd"
                ln -svf /mnt/sdisk /mnt/ssd
                log_ok "软链接已修正: /mnt/ssd -> /mnt/sdisk"
            else
                log_ok "软链接已存在且正确: /mnt/ssd -> /mnt/sdisk"
            fi
        elif [ -d "/mnt/ssd" ]; then
            local n=$(find /mnt/ssd -maxdepth 1 2>/dev/null | wc -l)
            if [ "$n" -le 1 ]; then
                rmdir "/mnt/ssd" 2>/dev/null || rm -rf "/mnt/ssd"
                ln -svf /mnt/sdisk /mnt/ssd
                log_ok "软链接: /mnt/ssd -> /mnt/sdisk"
            else
                log_w "/mnt/ssd 是普通目录且有数据，不自动操作，需手动处理:"
                log_w "  mv /mnt/ssd/* /mnt/sdisk/ && rm -rf /mnt/ssd && ln -s /mnt/sdisk /mnt/ssd"
            fi
        fi
    fi

    log_ok "降级环境准备完成"
    return 0
}

# ==================================================================
# 回退方案: tmpfs (当 loop 不可用时)
# ==================================================================
setup_tmpfs_fallback() {
    log_hdr "步骤5: 执行降级 (tmpfs 回退)"
    log_w "  使用 tmpfs (size=2M, rw)"

    [ -L "$SD_PATH" ] && rm -f "$SD_PATH"
    mkdir -p "$SD_PATH"
    umount "$SD_PATH" 2>/dev/null || true

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

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

    if [ ! -e "$LINK_DB" ]; then
        ln -svf "$DIR_DB_SD" "$LINK_DB"
        log_ok "软链接: $LINK_DB -> $DIR_DB_SD"
    fi

    if [ "$SD_PATH" = "/mnt/sdisk" ]; then
        if [ ! -e "/mnt/ssd" ]; then
            ln -svf /mnt/sdisk /mnt/ssd
            log_ok "软链接: /mnt/ssd -> /mnt/sdisk"
        elif [ -L "/mnt/ssd" ]; then
            local tgt=$(readlink "/mnt/ssd" 2>/dev/null)
            if [ "$tgt" != "/mnt/sdisk" ]; then
                rm -f "/mnt/ssd"
                ln -svf /mnt/sdisk /mnt/ssd
                log_ok "软链接已修正: /mnt/ssd -> /mnt/sdisk"
            fi
        fi
    fi

    log_ok "降级环境准备完成 (tmpfs 回退模式)"
    return 0
}

# ==================================================================
# 最终汇总输出
# ==================================================================
print_summary() {
    log "============================================================"
    log "执行结果"
    log "============================================================"

    if [ -L "$LINK_DB" ]; then
        local tgt=$(readlink -f "$LINK_DB" 2>/dev/null || readlink "$LINK_DB")
        log_i "database -> $tgt"
    elif [ -d "$LINK_DB" ]; then
        log_w "database 为普通目录 ($LINK_DB)"
    else
        log_e "database 不存在!"
    fi

    local BAK_PATH="${BACKUP_DIR}/${BACKUP_NAME}"
    if [ -d "$BAK_PATH" ]; then
        local bak_n=$(find "$BAK_PATH" -type f 2>/dev/null | wc -l)
        local bak_size=$(du -sh "$BAK_PATH" 2>/dev/null | awk '{print $1}')
        log_i "eMMC 备份: $BAK_PATH ($bak_n 个文件, $bak_size)"
    fi

    echo ""
    log "挂载状态:"
    mount | grep "$SD_PATH" | while read line; do log "  $line"; done
    echo ""
    df -h "$SD_PATH" 2>/dev/null | while read line; do log "  $line"; done

    echo ""
    if mount | grep "$SD_PATH" | grep -qE "loop|tmpfs"; then
        local mount_type="未知"
        if mount | grep "$SD_PATH" | grep -q "loop"; then
            mount_type="eMMC loop (${LOOP_SIZE}M)"
        elif mount | grep "$SD_PATH" | grep -q "tmpfs"; then
            mount_type="tmpfs (2M)"
        fi

        log_w "=========================================="
        log_w "  当前为降级模式 ($mount_type)"
        log_w "  程序可打开数据库并初始化"
        log_w "  大数据写入将返回 ENOSPC (空间不足)"
        log_w ""
        log_w "  恢复步骤 (插入 $SD_NAME 后):"
        log_w "    1. umount $SD_PATH"
        log_w "    2. 运行 ensure_storage_dir.sh 挂载真实卡"
        log_w "    3. 运行 fix_database_to_sd.sh 迁移备份数据"
        if [ -f "$LOOP_IMG" ]; then
            log_w "    4. 清理镜像: rm -f $LOOP_IMG"
        fi
        log_w "=========================================="
    fi

    log "完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
}

# ==================================================================
# 主流程
# ==================================================================
main() {
    log "ensure_storage_dir.sh 启动"
    log "开始时间: $(date '+%Y-%m-%d %H:%M:%S')"

    detect_device || exit 1

    check_card_status
    CARD_OK=$?

    diagnose_database
    DB_STATUS=$?

    echo ""
    log "============================================================"
    log "决策判断"
    log "============================================================"

    if [ "$CARD_OK" -eq 0 ]; then
        if [ "$DB_STATUS" -eq 0 ]; then
            log_ok "状态正常: 有卡且 database 正确，无需任何操作"
        else
            log_w "状态: 有卡但 database 需修复"
            log_w "建议运行 fix_database_to_sd.sh 进行修复"
            log_w "本脚本不处理有卡情况，退出"
        fi
        print_summary
        exit 0
    fi

    if [ "$DB_STATUS" -eq 0 ]; then
        if mount | grep "$SD_PATH" | grep -qE "tmpfs|loop"; then
            log_ok "状态正常: 无卡，降级环境已就绪"
            log_w "程序可运行，写入返回 ENOSPC"
        else
            log_w "状态异常: 无卡但 database 软链接指向不存在的路径"
            log_w "建议重新运行本脚本重建降级环境"
        fi
        print_summary
        exit 0
    fi

    log_w "状态: 无卡且需要营造降级环境"
    log_w "开始执行降级处理..."
    echo ""

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

    setup_loop_degrade || {
        log_e "降级环境创建失败"
        print_summary
        exit 1
    }

    print_summary
}

main "$@"

