#!/system/bin/sh

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

MODULE_NAME="${MODULE_NAME:-NBG}"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd)"
[ -n "$SCRIPT_DIR" ] || SCRIPT_DIR="."
SCRIPT_ARGS="$*"
LOG_FILE="${LOG_FILE:-$SCRIPT_DIR/${MODULE_NAME}_load_error.log}"
LOG_STARTED=0
ERROR_LOG_WRITTEN=0
DMESG_BEFORE_FILE="/data/local/tmp/${MODULE_NAME}_dmesg_before.log"
DMESG_LINES="${DMESG_LINES:-500}"

say() { printf "%b\n" "$*"; }

now_string() {
    date '+%Y-%m-%d %H:%M:%S %z' 2>/dev/null || date 2>/dev/null || echo unknown
}

print_env_plain() {
    echo "kernel : $(uname -r)"
    echo "android: $(getprop ro.build.version.release 2>/dev/null) sdk=$(android_api)"
    echo "arch   : $(uname -m)"
    echo "module : $MODULE_NAME"
}

ensure_log_file() {
    [ "$LOG_STARTED" = "1" ] && return 0

    : >"$LOG_FILE" 2>/dev/null || LOG_FILE="/data/local/tmp/${MODULE_NAME}_load_error.log"
    : >"$LOG_FILE" 2>/dev/null || LOG_FILE="/sdcard/${MODULE_NAME}_load_error.log"
    : >"$LOG_FILE" 2>/dev/null || {
        LOG_FILE=""
        return 1
    }

    LOG_STARTED=1
    ERROR_LOG_WRITTEN=1
    {
        echo "NBG driver error log"
        echo "created: $(now_string)"
        echo "script : $0"
        echo "args   : $SCRIPT_ARGS"
        echo
        echo "===== device ====="
        print_env_plain
    } >>"$LOG_FILE" 2>&1
    return 0
}

show_log_notice() {
    [ "$ERROR_LOG_WRITTEN" = "1" ] || return 0
    [ -n "$LOG_FILE" ] || return 0
    say "${YELLOW}日志已保存本地目录: \"$(basename "$LOG_FILE")\"${NC}"
    say "${YELLOW}请把这个日志发送给作者。${NC}"
    say "${YELLOW}请把这个日志发送给作者。${NC}"
    say "${YELLOW}请把这个日志发送给作者。${NC}"
}

need_root() {
    if [ "$(id -u)" != "0" ]; then
        say "${RED}错误: 请用 root 执行，例如 su -c sh $0${NC}"
        exit 1
    fi
}

ko_list() {
    find "$SCRIPT_DIR" -maxdepth 1 -type f -name '*.ko' 2>/dev/null | sort
}

android_api() {
    getprop ro.build.version.sdk 2>/dev/null
}

android_major() {
    api="$(android_api)"
    case "$api" in
        36) echo 16 ;;
        35) echo 15 ;;
        34) echo 14 ;;
        33) echo 13 ;;
        31|32) echo 12 ;;
        *) getprop ro.build.version.release 2>/dev/null | sed 's/\..*$//' ;;
    esac
}

kernel_short() {
    uname -r | sed -n 's/^\([0-9][0-9]*\.[0-9][0-9]*\).*/\1/p'
}

kernel_release() {
    uname -r
}

candidate_order() {
    r="$(kernel_release)"
    k="$(kernel_short)"
    a="$(android_major)"

    case "$k:$a" in
        5.10:13) preferred="5.10-a13.ko" ;;
        5.15:14) preferred="5.15-a14.ko" ;;
        *) preferred="$k.ko" ;;
    esac

    [ -f "$SCRIPT_DIR/$r.ko" ] && echo "$SCRIPT_DIR/$r.ko"
    [ -f "$SCRIPT_DIR/$preferred" ] && echo "$SCRIPT_DIR/$preferred"
    [ -f "$SCRIPT_DIR/$k.ko" ] && [ "$preferred" != "$k.ko" ] && echo "$SCRIPT_DIR/$k.ko"

    case "$k" in
        5.10)
            [ "$preferred" != "5.10-a13.ko" ] && [ -f "$SCRIPT_DIR/5.10-a13.ko" ] &&
                echo "$SCRIPT_DIR/5.10-a13.ko"
            ;;
        5.15)
            [ "$preferred" != "5.15-a14.ko" ] && [ -f "$SCRIPT_DIR/5.15-a14.ko" ] &&
                echo "$SCRIPT_DIR/5.15-a14.ko"
            ;;
    esac

    ko_list | while IFS= read -r f; do
        [ -n "$f" ] || continue
        b="$(basename "$f")"
        [ "$b" = "$r.ko" ] && continue
        [ "$b" = "$preferred" ] && continue
        [ "$b" = "$k.ko" ] && continue
        [ "$b" = "5.10-a13.ko" ] && [ "$k" = "5.10" ] && continue
        [ "$b" = "5.15-a14.ko" ] && [ "$k" = "5.15" ] && continue
        case "$b" in
            "$r"-*.ko) echo "$f" ;;
            "$k"-*.ko) echo "$f" ;;
        esac
    done
}

print_env() {
    say "${CYAN}设备信息:${NC}"
    print_env_plain | sed 's/^/  /'
}

show_ko_info() {
    f="$1"
    echo "  file   : $(basename "$f")"
    [ -r "$f" ] && ls -lh "$f" 2>/dev/null | awk '{print "  size   : "$5}'
    if command -v modinfo >/dev/null 2>&1; then
        vermagic="$(modinfo "$f" 2>/dev/null | sed -n 's/^vermagic:[[:space:]]*//p')"
        [ -n "$vermagic" ] && echo "  vermagic: $vermagic"
    fi
}

snapshot_dmesg() {
    if command -v dmesg >/dev/null 2>&1; then
        dmesg 2>/dev/null | tail -n "$DMESG_LINES"
    fi
}

save_dmesg_before() {
    if command -v dmesg >/dev/null 2>&1; then
        dmesg 2>/dev/null | tail -n "$DMESG_LINES" >"$DMESG_BEFORE_FILE" 2>/dev/null || true
    fi
}

print_dmesg_before() {
    if [ -r "$DMESG_BEFORE_FILE" ]; then
        cat "$DMESG_BEFORE_FILE" 2>/dev/null
    else
        echo "(unavailable)"
    fi
}

diagnose_failure() {
    f="$1"
    err="$2"

    if ensure_log_file; then
        {
            echo
            echo "===== insmod failed: $(basename "$f") ====="
            echo "time   : $(now_string)"
            echo "path   : $f"
            show_ko_info "$f"
            echo
            echo "insmod stderr:"
            if [ -n "$err" ]; then
                printf "%s\n" "$err"
            else
                echo "(empty)"
            fi
            echo
            echo "lsmod:"
            lsmod 2>&1
            echo
            echo "SELinux:"
            getenforce 2>&1
            echo
            echo "all ko in script dir:"
            ko_list 2>&1
            echo
            echo "dmesg before insmod tail ${DMESG_LINES} lines:"
            print_dmesg_before
            echo
            echo "dmesg after insmod tail ${DMESG_LINES} lines:"
            snapshot_dmesg
            echo
            echo "dmesg after insmod filtered:"
            snapshot_dmesg | grep -Ei 'NBG|module|vermagic|version magic|invalid|unknown symbol|disagrees|taint|exec format|permission|denied|Required key|signature|insmod|kallsyms|kprobe|BTF|BPF'
        } >>"$LOG_FILE" 2>&1
    fi

    return 0
}

log_no_candidate() {
    if ensure_log_file; then
        {
            echo
            echo "===== no candidate ====="
            echo "time        : $(now_string)"
            echo "kernel      : $(kernel_release)"
            echo "kernel_short: $(kernel_short)"
            echo "script_dir  : $SCRIPT_DIR"
            echo
            echo "all ko in script dir:"
            ko_list 2>&1
        } >>"$LOG_FILE" 2>&1
    fi
}

log_rmmod_failure() {
    err="$1"
    if ensure_log_file; then
        {
            echo
            echo "===== rmmod failed ====="
            echo "time  : $(now_string)"
            echo "module: $MODULE_NAME"
            echo
            echo "rmmod stderr:"
            if [ -n "$err" ]; then
                printf "%s\n" "$err"
            else
                echo "(empty)"
            fi
            echo
            echo "lsmod:"
            lsmod 2>&1
            echo
            echo "dmesg tail ${DMESG_LINES} lines:"
            snapshot_dmesg
        } >>"$LOG_FILE" 2>&1
    fi
}

is_loaded() {
    lsmod 2>/dev/null | awk '{print $1}' | grep -qx "$MODULE_NAME"
}

try_insmod() {
    f="$1"
    err_file="/data/local/tmp/${MODULE_NAME}_insmod.err"
    rm -f "$err_file"

    say "${YELLOW}尝试加载: $(basename "$f")${NC}"
    show_ko_info "$f"
    save_dmesg_before

    if insmod "$f" 2>"$err_file"; then
        say "${GREEN}加载成功: $(basename "$f")${NC}"
        return 0
    fi

    diagnose_failure "$f" "$(cat "$err_file" 2>/dev/null)"
    return 1
}

auto_install() {
    found=0
    tried=""

    if is_loaded; then
        say "${YELLOW}$MODULE_NAME 已在 lsmod 中显示为已加载。${NC}"
        return 0
    fi

    while IFS= read -r f; do
        [ -n "$f" ] || continue
        found=1
        tried="$tried
$f"
        try_insmod "$f" && return 0
    done <<EOF
$(candidate_order)
EOF

    if [ "$found" = "0" ]; then
        log_no_candidate
        say "${RED}未找到适配内核 $(kernel_short) 的 ko，目录: $SCRIPT_DIR${NC}"
        say "${YELLOW}当前目录 ko:${NC}"
        ko_list | sed 's|.*/|  - |'
        show_log_notice
        return 1
    fi


    say "${RED}刷入失败。${NC}"
    show_log_notice
    return 1
}

manual_install() {
    i=1
    files="$(ko_list)"
    [ -n "$files" ] || { say "${RED}当前目录没有 .ko 文件${NC}"; return 1; }

    say "${GREEN}可用驱动:${NC}"
    while IFS= read -r f; do
        [ -n "$f" ] || continue
        echo "  ($i) $(basename "$f")"
        i=$((i + 1))
    done <<EOF
$files
EOF
    max=$((i - 1))

    printf "请输入序号 (1-$max，0退出): "
    read choice
    choice="$(printf "%s" "$choice" | tr -d '\r')"
    [ "$choice" = "0" ] && return 0
    case "$choice" in ''|*[!0-9]*) say "${RED}输入无效${NC}"; return 1 ;; esac
    [ "$choice" -ge 1 ] 2>/dev/null && [ "$choice" -le "$max" ] 2>/dev/null || {
        say "${RED}序号超出范围${NC}"
        return 1
    }

    i=1
    while IFS= read -r f; do
        [ -n "$f" ] || continue
        if [ "$i" = "$choice" ]; then
            try_insmod "$f"
            rc=$?
            if [ "$rc" != "0" ]; then
                say "${RED}刷入失败。${NC}"
                show_log_notice
            fi
            return $rc
        fi
        i=$((i + 1))
    done <<EOF
$files
EOF
    return 1
}

uninstall_module() {
    say "${YELLOW}卸载模块: $MODULE_NAME${NC}"
    if rmmod "$MODULE_NAME" 2>/data/local/tmp/${MODULE_NAME}_rmmod.err; then
        say "${GREEN}卸载成功${NC}"
        return 0
    fi

    say "${RED}卸载失败${NC}"
    rmmod_err="$(cat /data/local/tmp/${MODULE_NAME}_rmmod.err 2>/dev/null)"
    [ -n "$rmmod_err" ] && printf "%s\n" "$rmmod_err" | sed 's/^/  rmmod: /'
    log_rmmod_failure "$rmmod_err"
    say "${YELLOW}如果模块已隐藏或仍被引用，通常需要重启。${NC}"
    snapshot_dmesg | grep -Ei 'NBG|module|rmmod|busy|in use|unload|refcount' | tail -n 40
    show_log_notice
    return 1
}

main_menu() {
    while true; do
        say ""
        say "${GREEN}请选择操作:${NC}"
        echo "  (1) 自动匹配并加载"
        echo "  (2) 手动选择加载"
        echo "  (3) 卸载 $MODULE_NAME"
        echo "  (4) 显示环境和候选"
        echo "  (0) 退出"
        printf "输入选项: "
        read op
        op="$(printf "%s" "$op" | tr -d '\r')"

        case "$op" in
            1) auto_install ;;
            2) manual_install ;;
            3) uninstall_module ;;
            4)
                print_env
                say "${CYAN}自动候选顺序:${NC}"
                candidate_order | sed 's|.*/|  - |'
                say "${CYAN}全部 ko:${NC}"
                ko_list | sed 's|.*/|  - |'
                ;;
            0) exit 0 ;;
            *) say "${RED}无效选项${NC}" ;;
        esac
    done
}

need_root
print_env

case "$1" in
    install|auto|"") auto_install ;;
    manual) manual_install ;;
    uninstall|remove|rm) uninstall_module ;;
    menu) main_menu ;;
    info) candidate_order | sed 's|.*/|  - |' ;;
    *) main_menu ;;
esac
