#!/system/bin/sh

set -u

STATE_DIR=${INTELLIRAT_STATE_DIR:-/data/local/tmp}
CONFIG_FILE=${INTELLIRAT_CONFIG:-$STATE_DIR/config.conf}
LOG_FILE=${INTELLIRAT_LOG:-$STATE_DIR/events.log}
CMD_BIN=${INTELLIRAT_CMD:-cmd}
DUMPSYS_BIN=${INTELLIRAT_DUMPSYS:-dumpsys}

DEFAULT_SLOT=0
DEFAULT_HIGH_MASK=00011101001110000111
DEFAULT_LOW_MASK=00011100001110000111

usage() {
    cat <<'EOF'
IntelliRAT v0.1 manual prototype

Usage:
  intellirat status [slot]
  intellirat capture [slot]
  intellirat high [slot]
  intellirat low [slot]
  intellirat restore [slot]
  intellirat --dry-run high|low|restore [slot]

The prototype never changes RAT automatically. Capture the current state before
the first transition. Restore returns the captured user network mask.
EOF
}

fail() {
    printf 'IntelliRAT: %s\n' "$*" >&2
    exit 1
}

load_config() {
    TARGET_SLOT=$DEFAULT_SLOT
    HIGH_MASK=$DEFAULT_HIGH_MASK
    LOW_MASK=$DEFAULT_LOW_MASK

    if [ -f "$CONFIG_FILE" ]; then
        # The file is created root-only by service.sh; reject unsafe ownership
        # only when the platform exposes stat in the available shell.
        # shellcheck disable=SC1090
        . "$CONFIG_FILE"
    fi

    case "$TARGET_SLOT" in
        ''|*[!0-9]*) fail "invalid TARGET_SLOT" ;;
    esac
    case "$HIGH_MASK:$LOW_MASK" in
        *[!01:]*|:*) fail "masks must be non-empty binary strings" ;;
    esac
}

ensure_state_dir() {
    mkdir -p "$STATE_DIR" || fail "cannot create $STATE_DIR"
    if [ "$STATE_DIR" = /data/local/tmp ]; then
        return
    fi
    chown 0 2000 "$STATE_DIR" 2>/dev/null || true
    chmod 0770 "$STATE_DIR" 2>/dev/null || true
}

log_event() {
    ensure_state_dir
    printf '%s %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*" >> "$LOG_FILE"
    chmod 0600 "$LOG_FILE" 2>/dev/null || true
}

slot_or_default() {
    if [ "${1:-}" = '' ]; then
        printf '%s' "$TARGET_SLOT"
    else
        case "$1" in
            ''|*[!0-9]*) fail "invalid slot: $1" ;;
        esac
        printf '%s' "$1"
    fi
}

state_file() {
    printf '%s/intellirat-sub-%s.state' "$STATE_DIR" "$1"
}

read_user_names() {
    slot=$1
    result=$(
        "$CMD_BIN" phone get-allowed-network-types-for-users -s "$slot" 2>&1
    ) || fail "cannot read network types for slot $slot: $result"
    case "$result" in
        *'No valid subscription found'*|*'No valid NETWORK_TYPES_BITMASK'*)
            fail "no valid subscription for slot $slot: $result"
            ;;
    esac
    result=$(printf '%s' "$result" | tr -d '[:space:]\r\n')
    [ -n "$result" ] || fail "empty network type response for slot $slot"
    printf '%s' "$result"
}

network_names_to_decimal() {
    names=$1
    mask=0
    old_ifs=$IFS
    IFS='|'
    # shellcheck disable=SC2086
    for name in $names; do
        case "$name" in
            GPRS) mask=$((mask | 1)) ;;
            EDGE) mask=$((mask | 2)) ;;
            UMTS) mask=$((mask | 4)) ;;
            CDMA) mask=$((mask | 8)) ;;
            EVDO_0) mask=$((mask | 16)) ;;
            EVDO_A) mask=$((mask | 32)) ;;
            1xRTT) mask=$((mask | 64)) ;;
            HSDPA) mask=$((mask | 128)) ;;
            HSUPA) mask=$((mask | 256)) ;;
            HSPA) mask=$((mask | 512)) ;;
            HSPA+) mask=$((mask | 16384)) ;;
            IDEN) mask=$((mask | 1024)) ;;
            EVDO_B) mask=$((mask | 2048)) ;;
            LTE) mask=$((mask | 4096)) ;;
            EHRPD) mask=$((mask | 8192)) ;;
            HSPAP) mask=$((mask | 16384)) ;;
            GSM) mask=$((mask | 32768)) ;;
            TD_SCDMA) mask=$((mask | 65536)) ;;
            IWLAN) mask=$((mask | 131072)) ;;
            LTE_CA) mask=$((mask | 4096)) ;;
            NR) mask=$((mask | 524288)) ;;
            UNKNOWN|NONE) ;;
            *) IFS=$old_ifs; fail "unknown network type from telephony: $name" ;;
        esac
    done
    IFS=$old_ifs
    printf '%s' "$mask"
}

decimal_to_binary() {
    mask=$1
    output=
    bit=19
    while [ "$bit" -ge 0 ]; do
        value=$((1 << bit))
        if [ $((mask & value)) -ne 0 ]; then
            output="${output}1"
        else
            output="${output}0"
        fi
        bit=$((bit - 1))
    done
    printf '%s' "$output"
}

read_user_binary() {
    names=$(read_user_names "$1")
    decimal=$(network_names_to_decimal "$names")
    decimal_to_binary "$decimal"
}

call_in_progress() {
    dump=$(
        "$DUMPSYS_BIN" telecom 2>/dev/null || true
    )
    current_calls=$(printf '%s\n' "$dump" | awk '
        /^    All calls:/ { in_calls=1; next }
        /^    Active dialing, or connecting calls:/ { in_calls=1; next }
        /^    Ringing calls:/ { in_calls=1; next }
        /^    Holding calls:/ { in_calls=1; next }
        /^[[:space:]]*[A-Za-z][A-Za-z _-]*:$/ { in_calls=0 }
        in_calls { print }
    ')
    printf '%s\n' "$current_calls" | grep -Eq \
        '(^|[^A-Za-z])(ACTIVE|RINGING|DIALING|CONNECTING)([^A-Za-z]|$)|mCallState=(ACTIVE|RINGING|DIALING|CONNECTING)'
}

write_state() {
    slot=$1
    original_names=$2
    original_binary=$3
    file=$(state_file "$slot")
    tmp="${file}.tmp.$$"
    {
        printf 'original_names=%s\n' "$original_names"
        printf 'original_mask=%s\n' "$original_binary"
        printf 'captured_at=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
    } > "$tmp" || fail "cannot write $file"
    chmod 0600 "$tmp"
    mv "$tmp" "$file" || fail "cannot commit $file"
}

capture() {
    slot=$1
    ensure_state_dir
    names=$(read_user_names "$slot")
    decimal=$(network_names_to_decimal "$names")
    binary=$(decimal_to_binary "$decimal")
    file=$(state_file "$slot")
    if [ -f "$file" ] && [ "${INTELLIRAT_FORCE_CAPTURE:-0}" != 1 ]; then
        fail "state already captured for slot $slot; set INTELLIRAT_FORCE_CAPTURE=1 to replace it"
    fi
    write_state "$slot" "$names" "$binary"
    log_event "capture slot=$slot mask=$binary names=$names"
    printf 'captured slot=%s mask=%s names=%s\n' "$slot" "$binary" "$names"
}

get_original_mask() {
    file=$(state_file "$1")
    [ -f "$file" ] || {
        printf '%s' "$HIGH_MASK"
        return
    }
    value=$(grep '^original_mask=' "$file" | cut -d= -f2-)
    case "$value" in
        ''|*[!01]*) fail "invalid saved state for slot $1" ;;
    esac
    printf '%s' "$value"
}

apply_mask() {
    slot=$1
    target=$2
    reason=$3
    current=$(read_user_binary "$slot")
    if [ "$current" = "$target" ]; then
        log_event "noop slot=$slot state=$reason mask=$target"
        printf 'already state=%s slot=%s mask=%s\n' "$reason" "$slot" "$target"
        return 0
    fi

    if call_in_progress; then
        fail "telephony call is active; transition refused"
    fi

    if [ "${INTELLIRAT_DRY_RUN:-0}" = 1 ]; then
        printf 'dry-run cmd phone set-allowed-network-types-for-users -s %s %s\n' \
            "$slot" "$target"
        return 0
    fi

    result=$(
        "$CMD_BIN" phone set-allowed-network-types-for-users -s "$slot" "$target" 2>&1
    ) || fail "network transition failed: $result"
    case "$result" in
        *completed*) ;;
        *) fail "telephony did not confirm transition: $result" ;;
    esac

    actual=$(read_user_binary "$slot")
    [ "$actual" = "$target" ] || fail "verification mismatch: requested=$target actual=$actual"
    log_event "transition slot=$slot previous=$current state=$reason requested=$target actual=$actual"
    printf 'transition slot=%s state=%s previous=%s actual=%s\n' \
        "$slot" "$reason" "$current" "$actual"
}

status() {
    slot=$1
    names=$(read_user_names "$slot")
    decimal=$(network_names_to_decimal "$names")
    current=$(decimal_to_binary "$decimal")
    state=UNKNOWN
    [ "$current" = "$HIGH_MASK" ] && state=HIGH
    [ "$current" = "$LOW_MASK" ] && state=LOW
    saved=NO
    [ -f "$(state_file "$slot")" ] && saved=YES
    printf 'slot=%s state=%s mask=%s names=%s saved_original=%s\n' \
        "$slot" "$state" "$current" "$names" "$saved"
}

main() {
    dry_run=0
    if [ "${1:-}" = '--dry-run' ]; then
        dry_run=1
        INTELLIRAT_DRY_RUN=1
        export INTELLIRAT_DRY_RUN
        shift
    fi
    load_config
    ensure_state_dir

    command=${1:-help}
    shift || true
    slot=$(slot_or_default "${1:-}")

    case "$command" in
        help|-h|--help) usage ;;
        status) status "$slot" ;;
        capture) capture "$slot" ;;
        high) apply_mask "$slot" "$HIGH_MASK" HIGH ;;
        low) apply_mask "$slot" "$LOW_MASK" LOW ;;
        restore) apply_mask "$slot" "$(get_original_mask "$slot")" RESTORE ;;
        *) usage >&2; exit 2 ;;
    esac
}

main "$@"
