#!/bin/sh
# Bananarchy Dual-SIM & QMI UIM Provisioning Helper for Qualcomm MSM8905/MSM8909
set -eu

CONF_FILE="/etc/conf.d/msm-modem-uim-selection"
if [ -r "$CONF_FILE" ]; then
  # shellcheck disable=SC1090
  . "$CONF_FILE"
fi

SIM_WAIT_TIME="${SIM_WAIT_TIME:-${sim_wait_time:-4}}"
PREFERRED_SIM_SLOT="${PREFERRED_SIM_SLOT:-${preferred_sim_slot:-1}}"
QMICLI_MODEM=""

detect_qmicli_modem() {
  if ! command -v qmicli >/dev/null 2>&1; then
    return 1
  fi
  if [ -e "/dev/modem" ]; then
    QMICLI_MODEM="qmicli --silent -d /dev/modem"
    return 0
  elif [ -e "/dev/wwan0qmi0" ]; then
    QMICLI_MODEM="qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi"
    return 0
  elif qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
    QMICLI_MODEM="qmicli --silent -pd qrtr://0"
    return 0
  fi
  return 1
}

wait_for_qmicli_modem() {
  max_wait="${1:-45}"
  count=0
  while [ "$count" -lt "$max_wait" ]; do
    if detect_qmicli_modem; then
      return 0
    fi
    sleep 1
    count=$((count + 1))
  done
  return 1
}

# Parse `qmicli --uim-get-card-status` into shell-safe KEY=VALUE lines
parse_uim_card_status() {
  raw_status="$1"
  pref_slot="$2"
  printf "%s\n" "$raw_status" | awk -v pref="$pref_slot" '
    BEGIN {
      prim_slot = 0
      prim_app = 0
      cur_slot = 0
      in_app = 0
      for (s = 1; s <= 2; s++) {
        cstate[s] = "absent"
        rawstate[s] = "absent"
        upin_state[s] = "not-initialized"
        upin_ret[s] = 0
        upuk_ret[s] = 0
        app_type[s] = "none"
        app_state[s] = "none"
        aid[s] = ""
        pin1_state[s] = "not-initialized"
        pin1_ret[s] = 0
        puk1_ret[s] = 0
      }
    }
    /Primary GW:/ {
      if ($0 ~ /slot \x27[0-9]+\x27/) {
        line = $0
        sub(/.*slot \x27/, "", line)
        sub(/\x27.*/, "", line)
        prim_slot = line + 0
      }
      if ($0 ~ /application \x27[0-9]+\x27/) {
        line = $0
        sub(/.*application \x27/, "", line)
        sub(/\x27.*/, "", line)
        prim_app = line + 0
      }
    }
    /^[[:space:]]*Slot \[[0-9]+\]:/ {
      line = $0
      sub(/.*Slot \[/, "", line)
      sub(/\].*/, "", line)
      cur_slot = line + 0
      in_app = 0
      next
    }
    cur_slot >= 1 && cur_slot <= 2 {
      if ($0 ~ /^[[:space:]]*Application \[[0-9]+\]:/) {
        in_app = 1
        next
      }
      if (!in_app && $0 ~ /Card state:/) {
        line = $0
        sub(/.*Card state: \x27/, "", line)
        sub(/\x27.*/, "", line)
        rawstate[cur_slot] = line
        if (line == "present") cstate[cur_slot] = "present"
        else if (line == "absent" || line ~ /no-atr-received/) cstate[cur_slot] = "absent"
        else cstate[cur_slot] = "error"
      }
      if (!in_app && $0 ~ /UPIN state:/) {
        line = $0
        sub(/.*UPIN state: \x27/, "", line)
        sub(/\x27.*/, "", line)
        upin_state[cur_slot] = line
      }
      if (!in_app && $0 ~ /UPIN retries:/) {
        line = $0
        sub(/.*UPIN retries: \x27/, "", line)
        sub(/\x27.*/, "", line)
        upin_ret[cur_slot] = line + 0
      }
      if (!in_app && $0 ~ /UPUK retries:/) {
        line = $0
        sub(/.*UPUK retries: \x27/, "", line)
        sub(/\x27.*/, "", line)
        upuk_ret[cur_slot] = line + 0
      }
      if (in_app && $0 ~ /Application type:/) {
        line = $0
        sub(/.*Application type: \x27/, "", line)
        sub(/\x27.*/, "", line)
        if (line ~ /^usim/) {
          app_type[cur_slot] = "usim"
          want_aid[cur_slot] = 2
        } else if (line ~ /^sim/ && app_type[cur_slot] != "usim") {
          app_type[cur_slot] = "sim"
          want_aid[cur_slot] = 1
        } else {
          want_aid[cur_slot] = 0
        }
      }
      if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*Application state: \x27/, "", line)
        sub(/\x27.*/, "", line)
        app_state[cur_slot] = line
      }
      if (in_app && $0 ~ /Application ID:/ && want_aid[cur_slot] > 0) {
        need_aid_line[cur_slot] = 1
        next
      }
      if (in_app && need_aid_line[cur_slot] == 1) {
        line = $0
        gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
        if (line != "") {
          aid[cur_slot] = line
        }
        need_aid_line[cur_slot] = 0
      }
      if (in_app && $0 ~ /PIN1 state:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PIN1 state: \x27/, "", line)
        sub(/\x27.*/, "", line)
        pin1_state[cur_slot] = line
      }
      if (in_app && $0 ~ /PIN1 retries:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PIN1 retries: \x27/, "", line)
        sub(/\x27.*/, "", line)
        pin1_ret[cur_slot] = line + 0
      }
      if (in_app && $0 ~ /PUK1 retries:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PUK1 retries: \x27/, "", line)
        sub(/\x27.*/, "", line)
        puk1_ret[cur_slot] = line + 0
      }
    }
    END {
      printf "PREFERRED_SLOT=%d\n", (pref == 2 ? 2 : 1)
      printf "PRIMARY_GW_SLOT=%d\n", prim_slot
      printf "PRIMARY_GW_APP=%d\n", prim_app
      for (s = 1; s <= 2; s++) {
        printf "SLOT%d_STATE=%s\n", s, cstate[s]
        printf "SLOT%d_RAW_STATE=%s\n", s, rawstate[s]
        printf "SLOT%d_APP_TYPE=%s\n", s, app_type[s]
        printf "SLOT%d_APP_STATE=%s\n", s, app_state[s]
        printf "SLOT%d_AID=%s\n", s, aid[s]
        printf "SLOT%d_PIN1_STATE=%s\n", s, pin1_state[s]
        printf "SLOT%d_PIN1_RETRIES=%d\n", s, pin1_ret[s]
        printf "SLOT%d_PUK1_RETRIES=%d\n", s, puk1_ret[s]
        printf "SLOT%d_UPIN_STATE=%s\n", s, upin_state[s]
        printf "SLOT%d_UPIN_RETRIES=%d\n", s, upin_ret[s]
        printf "SLOT%d_UPUK_RETRIES=%d\n", s, upuk_ret[s]
      }
    }
  '
}

save_preferred_slot() {
  target_slot="$1"
  if [ -f "$CONF_FILE" ]; then
    if grep -q "^preferred_sim_slot=" "$CONF_FILE"; then
      sed -i "s/^preferred_sim_slot=.*/preferred_sim_slot=$target_slot/" "$CONF_FILE"
    else
      printf "\npreferred_sim_slot=%s\n" "$target_slot" >> "$CONF_FILE"
    fi
  else
    printf "sim_wait_time=4\npreferred_sim_slot=%s\n" "$target_slot" > "$CONF_FILE"
  fi
}

provision_slot() {
  req_slot="$1"
  wait_secs="$2"

  if ! wait_for_qmicli_modem "$wait_secs"; then
    echo "No modem available." >&2
    return 2
  fi

  QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
  count=0
  while ! printf "%s" "$QMI_CARDS" | grep -Fq "Card state: 'present'"; do
    if [ "$count" -ge "$SIM_WAIT_TIME" ]; then
      echo "No SIM detected after $SIM_WAIT_TIME seconds."
      return 0
    fi
    sleep 1
    count=$((count + 1))
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
  done

  eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"

  chosen_slot=""
  chosen_aid=""
  if [ "$req_slot" = "2" ]; then
    if [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then
      chosen_slot="2"
      chosen_aid="$SLOT2_AID"
    elif [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then
      chosen_slot="1"
      chosen_aid="$SLOT1_AID"
    fi
  else
    if [ "$SLOT1_STATE" = "present" ] && [ -n "$SLOT1_AID" ]; then
      chosen_slot="1"
      chosen_aid="$SLOT1_AID"
    elif [ "$SLOT2_STATE" = "present" ] && [ -n "$SLOT2_AID" ]; then
      chosen_slot="2"
      chosen_aid="$SLOT2_AID"
    fi
  fi

  if [ -z "$chosen_slot" ] || [ -z "$chosen_aid" ]; then
    echo "No valid USIM/SIM application found on any slot."
    return 0
  fi

  if [ "$PRIMARY_GW_SLOT" != "0" ]; then
    $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
  fi

  echo "Selecting AID $chosen_aid on slot $chosen_slot"
  $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning,aid=$chosen_aid" >/dev/null
  return $?
}

cmd="${1:-boot}"

case "$cmd" in
  boot)
    if command -v systemd-notify >/dev/null 2>&1; then
      trap '[ $? -eq 0 ] && systemd-notify --ready' EXIT
    fi
    case "$(cat /sys/devices/soc0/machine 2>/dev/null || true)" in
      APQ*)
        echo "Skipping SIM configuration on APQ SoC."
        exit 0
        ;;
    esac
    provision_slot "$PREFERRED_SIM_SLOT" 45
    exit $?
    ;;

  status)
    if ! detect_qmicli_modem; then
      echo "MODEM_AVAILABLE=0"
      echo "PREFERRED_SLOT=$PREFERRED_SIM_SLOT"
      exit 0
    fi
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
    if [ -z "$QMI_CARDS" ]; then
      echo "MODEM_AVAILABLE=0"
      echo "PREFERRED_SLOT=$PREFERRED_SIM_SLOT"
      exit 0
    fi
    echo "MODEM_AVAILABLE=1"
    parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT"
    exit 0
    ;;

  switch)
    target="${2:-1}"
    if [ "$target" != "1" ] && [ "$target" != "2" ]; then
      echo "Invalid slot: $target (must be 1 or 2)" >&2
      exit 1
    fi
    save_preferred_slot "$target"
    SIM_WAIT_TIME=2
    provision_slot "$target" 5
    if command -v rc-service >/dev/null 2>&1; then
      rc-service modemmanager restart >/dev/null 2>&1 || true
    elif command -v systemctl >/dev/null 2>&1; then
      systemctl restart ModemManager >/dev/null 2>&1 || true
    fi
    exit 0
    ;;

  verify-pin)
    slot="${2:-1}"
    pin="${3:-}"
    if [ -z "$pin" ]; then
      echo "Missing PIN" >&2
      exit 1
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ] && mmcli -i "$sim_id" --pin="$pin" >/dev/null 2>&1; then
      exit 0
    fi
    if detect_qmicli_modem; then
      if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    exit 1
    ;;

  unblock-puk)
    slot="${2:-1}"
    puk="${3:-}"
    newpin="${4:-}"
    if [ -z "$puk" ] || [ -z "$newpin" ]; then
      echo "Missing PUK or new PIN" >&2
      exit 1
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ] && mmcli -i "$sim_id" --puk="$puk" --pin="$newpin" >/dev/null 2>&1; then
      exit 0
    fi
    if detect_qmicli_modem; then
      if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    exit 1
    ;;

  set-pin-lock)
    slot="${2:-1}"
    mode="${3:-enable}"
    pin="${4:-}"
    if [ -z "$pin" ] || { [ "$mode" != "enable" ] && [ "$mode" != "disable" ]; }; then
      echo "Usage: $0 set-pin-lock <1|2> <enable|disable> <pin>" >&2
      exit 1
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ]; then
      if [ "$mode" = "enable" ] && mmcli -i "$sim_id" --enable-pin --pin="$pin" >/dev/null 2>&1; then
        exit 0
      elif [ "$mode" = "disable" ] && mmcli -i "$sim_id" --disable-pin --pin="$pin" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    if detect_qmicli_modem; then
      if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    exit 1
    ;;

  change-pin)
    slot="${2:-1}"
    oldpin="${3:-}"
    newpin="${4:-}"
    if [ -z "$oldpin" ] || [ -z "$newpin" ]; then
      echo "Usage: $0 change-pin <1|2> <oldpin> <newpin>" >&2
      exit 1
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ] && mmcli -i "$sim_id" --change-pin="$newpin" --pin="$oldpin" >/dev/null 2>&1; then
      exit 0
    fi
    if detect_qmicli_modem; then
      if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    exit 1
    ;;

  imei)
    imei1=""
    imei2=""
    imei_sv=""
    if detect_qmicli_modem; then
      dms_ids="$($QMICLI_MODEM --dms-get-ids 2>/dev/null || true)"
      imei1="$(printf "%s\n" "$dms_ids" | sed -n "s/.*IMEI: '\([0-9]\{14,16\}\)'/\1/p" | head -n1)"
      imei_sv="$(printf "%s\n" "$dms_ids" | sed -n "s/.*IMEI SV: '\([^']*\)'/\1/p" | head -n1)"
    fi
    if [ -z "$imei1" ]; then
      imei1="$(mmcli -m any -K 2>/dev/null | awk -F': ' '/modem\.3gpp\.imei/ {print $2; exit}')"
    fi
    # Query secondary AT channel (/dev/wwan0at1) if present
    if [ -c "/dev/wwan0at1" ]; then
      at_out="$(timeout 1 sh -c '
        stty -F /dev/wwan0at1 raw -echo 115200 2>/dev/null || true
        (cat /dev/wwan0at1 & pid=$!; printf "AT+CGSN\r\n" > /dev/wwan0at1; sleep 0.25; kill $pid 2>/dev/null) 2>/dev/null
      ' 2>/dev/null | tr -d "\r" | grep -oE '^[0-9]{15}$' | head -n1 || true)"
      if [ -n "$at_out" ] && [ "$at_out" != "$imei1" ]; then
        imei2="$at_out"
      fi
    fi
    # Scan factory partitions for Luhn-valid 15-digit IMEIs (TAC 35...)
    if [ -z "$imei2" ]; then
      for part in /dev/disk/by-partlabel/traceability /dev/disk/by-partlabel/tunning /dev/disk/by-partlabel/devinfo /dev/disk/by-partlabel/oem; do
        [ -e "$part" ] || continue
        cand_list="$(strings -n 14 "$part" 2>/dev/null | grep -oE '35[0-9]{13}' | awk '
          {
            val = $0
            sum = 0
            for (i = 1; i <= 14; i++) {
              d = substr(val, i, 1) + 0
              if (i % 2 == 0) {
                d *= 2
                if (d > 9) d -= 9
              }
              sum += d
            }
            chk = (10 - (sum % 10)) % 10
            if (chk == (substr(val, 15, 1) + 0)) print val
          }
        ' || true)"
        for c in $cand_list; do
          if [ -z "$imei1" ]; then
            imei1="$c"
          elif [ "$c" != "$imei1" ] && [ -z "$imei2" ]; then
            imei2="$c"
            break 2
          fi
        done
      done
    fi
    echo "IMEI1=${imei1}"
    echo "IMEI2=${imei2}"
    echo "IMEI_SV=${imei_sv}"
    ;;

  usb-tether)
    mode="${2:-status}"
    case "$mode" in
      on)
        sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 || true
        nft add table ip nat 2>/dev/null || true
        nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }' 2>/dev/null || true
        nft add rule ip nat postrouting oifname "wwan*" masquerade 2>/dev/null || true
        nft add rule ip nat postrouting oifname "wlan*" masquerade 2>/dev/null || true
        ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
        ip link set usb0 up 2>/dev/null || true
        echo "1" > /tmp/bananarchy_usb_tether.active
        ;;
      off)
        sysctl -w net.ipv4.ip_forward=0 >/dev/null 2>&1 || true
        nft delete table ip nat 2>/dev/null || true
        nmcli connection delete USBNetworkShare >/dev/null 2>&1 || true
        ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
        ip link set usb0 up 2>/dev/null || true
        rm -f /tmp/bananarchy_usb_tether.active
        ;;
      *)
        if [ -f /tmp/bananarchy_usb_tether.active ] && [ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null || echo 0)" = "1" ]; then
          echo "ACTIVE=1"
        else
          echo "ACTIVE=0"
        fi
        ;;
    esac
    ;;

  at)
    port="${2:-auto}"
    shift 2 2>/dev/null || true
    at_cmd="$*"
    if [ -z "$at_cmd" ]; then
      echo "ERROR: Empty AT command"
      exit 1
    fi
    run_at_dev() {
      dev="$1"
      [ -c "$dev" ] || return 1
      out="$(timeout 2 awk -v dev="$dev" -v cmd="$at_cmd" '
        BEGIN {
          sys = "stty -F " dev " raw -echo 115200 min 0 time 2 2>/dev/null"
          system(sys)
          printf "%s\r\n", cmd > dev
          close(dev)
        }
      ' 2>/dev/null || true)"
      return 0
    }
    resp=""
    for candidate in "$port" wwan0at1 wwan0at0; do
      case "$candidate" in
        wwan0at0|wwan0at1)
          dev="/dev/$candidate"
          if [ -c "$dev" ]; then
            resp="$(timeout 2 sh -c '
              dev="$1"
              cmd="$2"
              stty -F "$dev" raw -echo 115200 min 0 time 2 2>/dev/null || true
              exec 3<>"$dev"
              dd bs=256 count=4 status=none <&3 >/dev/null 2>&1 || true
              printf "%s\r\n" "$cmd" >&3
              lines=""
              i=0
              while [ "$i" -lt 8 ]; do
                chunk="$(dd bs=512 count=1 status=none <&3 2>/dev/null | tr -d "\r" || true)"
                if [ -n "$chunk" ]; then
                  lines="${lines}${chunk}"
                  case "$lines" in
                    *OK*|*ERROR*)
                      break
                      ;;
                  esac
                fi
                i=$((i + 1))
              done
              exec 3>&-
              printf "%s\n" "$lines"
            ' _ "$dev" "$at_cmd" 2>/dev/null | sed '/^[[:space:]]*$/d' || true)"
            if [ -n "$resp" ]; then
              printf "%s\n" "$resp"
              exit 0
            fi
          fi
          ;;
      esac
      [ "$port" != "auto" ] && [ "$candidate" = "$port" ] && break
    done
    mm_out="$(mmcli -m any --command="$at_cmd" 2>&1 || true)"
    if [ -n "$mm_out" ]; then
      printf "%s\n" "$mm_out" | sed "s/^response: '\(.*\)'$/\1/"
      exit 0
    fi
    echo "ERROR: No response from modem AT channel"
    exit 1
    ;;

  *)
    echo "Usage: $0 [boot|status|switch <1|2>|verify-pin <slot> <pin>|unblock-puk <slot> <puk> <newpin>|set-pin-lock <slot> <enable|disable> <pin>|change-pin <slot> <oldpin> <newpin>|imei|usb-tether <on|off|status>|at <port> <cmd>]" >&2
    exit 1
    ;;
esac
