#!/bin/sh
# naixi — iKuai app manager CLI
# Subcommands:
#   naixi install <file.ipkg|url>   Install and start
#   naixi uninstall <name>          Remove by name
#   naixi list                      List installed apps
#   naixi version                   Show version
#   naixi self-install              Register self in appmarket
#   naixi self-uninstall            Remove self
NAIXI_VERSION="0.4.3"
DL_BASE="https://dl.naixi.net/ikuai"
APPMARKET_DB="/etc/mnt/ikuai/appmarket.db"
DOCKER_DB="/etc/mnt/ikuai/docker.db"
IK_DIR="/usr/ikuai"
SELF_NAME="naixi"

set -eu

log()  { echo "[$SELF_NAME] $*"; }
err()  { echo "[$SELF_NAME] ERROR: $*" >&2; exit 1; }

# ─── json parse (awk fallback) ────────────────────────────────────
json_str() {
  awk -F'"' "/\"$1\"/{print \$4; exit}" "$2" 2>/dev/null
}

# ─── self-install: register naixi itself in appmarket.db ──────────
self_install() {
  WORKDISK=$(sqlite3 "$DOCKER_DB" "select workdisk from global" 2>/dev/null) || err "cannot read docker.db"
  APPS_DIR="/etc/disk_user/$WORKDISK/apps"
  APP_PATH="$APPS_DIR/$SELF_NAME"
  APPMARKET_DIR="/var/appmarket"

  # Only create structure if missing (naixi installed standalone, not via ipkg)
  if [ ! -d "$APP_PATH/.pkg/scripts" ]; then
    mkdir -p "$APP_PATH/.pkg/scripts" "$APP_PATH/.pkg/app/config" "$APP_PATH/log"
    ln -sf .pkg/app/config "$APP_PATH/config"

    # Minimal start.sh / stop.sh (naixi has no daemon besides ttyd which start.sh manages)
    cat > "$APP_PATH/.pkg/scripts/start.sh" <<'EOF'
#!/bin/sh
# noop — naixi is a CLI tool, ttyd is started by the ipkg start.sh
echo "0" > "$(dirname "$0")/../../log/run.status"
exit 0
EOF
    chmod +x "$APP_PATH/.pkg/scripts/start.sh"

    cat > "$APP_PATH/.pkg/scripts/stop.sh" <<'EOF'
#!/bin/sh
exit 0
EOF
    chmod +x "$APP_PATH/.pkg/scripts/stop.sh"
  fi

  # DO NOT overwrite manifest.json — the ipkg-installed one has integrity.files
  # which appmarket requires for origin=0 boot verification. Overwriting it with
  # a stripped manifest causes "integrity.files not found" → app won't start on reboot.

  # Allocate appid
  cur=100000
  [ -f "/etc/mnt/ikuai/appmarket/.max_appid" ] && cur=$(cat "/etc/mnt/ikuai/appmarket/.max_appid" 2>/dev/null || echo 100000)
  dbmax=$(sqlite3 "$APPMARKET_DB" "select max(appid) from applist" 2>/dev/null || echo 0)
  [ "$dbmax" -gt "$cur" ] 2>/dev/null && cur=$dbmax
  APPID=$((cur + 1))
  echo "$APPID" > "/etc/mnt/ikuai/appmarket/.max_appid" 2>/dev/null || true

  # Symlink
  mkdir -p "$APPMARKET_DIR"
  [ ! -e "$APPMARKET_DIR/$SELF_NAME" ] && ln -s "$APP_PATH" "$APPMARKET_DIR/$SELF_NAME"

  # Register in db (origin=0 so it survives reboot)
  TIMESTAMP=$(date +%s)
  sqlite3 "$APPMARKET_DB" "DELETE FROM applist WHERE name='$SELF_NAME'" 2>/dev/null || true
  sqlite3 "$APPMARKET_DB" "INSERT INTO applist (
    enabled,name,appid,timestamp,display_name,location,ico,type,size,
    restart,current_version,maintainer,maintainer_url,distributor,distributor_url,
    origin,update_time,install_path,category,sub_category,desc,readme,changelog
  ) VALUES (
    'yes','$SELF_NAME',$APPID,$TIMESTAMP,'Naixi','','',0,0,
    0,'$NAIXI_VERSION','Nyarime','https://naixi.net','Nyarime','https://naixi.net',
    0,$TIMESTAMP,'$WORKDISK',7,0,'TmFpeGkg5byV5aiB55qE5Zmo','',''
  )" 2>/dev/null || err "failed to write appmarket.db"

  echo "0" > "$APP_PATH/log/run.status"
  log "self-registered (appid=$APPID, origin=0)"
}

self_uninstall() {
  APP_PATH=$(sqlite3 "$APPMARKET_DB" "SELECT '/etc/disk_user/'||install_path||'/apps/'||name FROM applist WHERE name='$SELF_NAME'" 2>/dev/null)
  sqlite3 "$APPMARKET_DB" "DELETE FROM applist WHERE name='$SELF_NAME'" 2>/dev/null || true
  rm -rf "$APP_PATH" "/var/appmarket/$SELF_NAME" "$IK_DIR/www/static/appmarket/$SELF_NAME"
  log "self-unregistered"
}

# ─── install <ipkg|url> [-official] ────────────────────────────────
do_install() {
  IPKG="$1"
  shift 2>/dev/null
  # Parse remaining flags
  # Default origin=0 (official) because origin=3 (sideload) packages are NOT
  # auto-started by appmarket on boot — only origin=0 survives reboot.
  NAIXI_ORIGIN=0
  for flag in "$@"; do
    case "$flag" in
      -official|--official) NAIXI_ORIGIN=0 ;;
      -sideload|--sideload) NAIXI_ORIGIN=3 ;;
      -origin=*) NAIXI_ORIGIN="${flag#*=}" ;;
    esac
  done
  WORKDISK=$(sqlite3 "$DOCKER_DB" "select workdisk from global" 2>/dev/null) || err "cannot read docker.db"
  APPS_DIR="/etc/disk_user/$WORKDISK/apps"
  APPMARKET_DIR="/var/appmarket"
  MAX_APPID_FILE="/etc/mnt/ikuai/appmarket/.max_appid"

  # URL → download first
  case "$IPKG" in
    http://*|https://*)
      log "downloading $IPKG..."
      TMPFILE="/tmp/naixi-dl-$$.ipkg"
      if command -v curl >/dev/null 2>&1; then
        curl -fsSL "$IPKG" -o "$TMPFILE" || err "download failed"
      else
        wget -qO "$TMPFILE" "$IPKG" || err "download failed"
      fi
      IPKG="$TMPFILE"
      NAIXI_CLEANUP="$TMPFILE"
      ;;
  esac
  [ -f "$IPKG" ] || err "file not found: $IPKG"

  # Extract
  TMP=$(mktemp -d 2>/dev/null)
  [ -z "$TMP" ] && TMP="/tmp/naixi-$$" && mkdir -p "$TMP"
  trap 'rm -rf "$TMP" ${NAIXI_CLEANUP:-}' EXIT

  log "extracting..."
  tar xzf "$IPKG" -C "$TMP" 2>/dev/null || err "extraction failed"

  PKG_NAME=$(ls "$TMP")
  [ -d "$TMP/$PKG_NAME" ] || err "bad package structure"
  SRC="$TMP/$PKG_NAME"
  MANIFEST="$SRC/manifest.json"
  [ -f "$MANIFEST" ] || err "manifest.json not found"

  # Parse manifest
  VERSION=$(json_str version "$MANIFEST")
  DISPLAY_NAME=$(json_str display_name "$MANIFEST")
  [ -z "$DISPLAY_NAME" ] && DISPLAY_NAME=$(json_str name "$MANIFEST")
  DESCRIPTION=$(json_str description "$MANIFEST")
  TYPE=$(json_str type "$MANIFEST")
  [ -z "$TYPE" ] && TYPE=0
  MAINTAINER=$(json_str maintainer "$MANIFEST")
  MAINTAINER_URL=$(json_str maintainer_url "$MANIFEST")
  DISTRIBUTOR=$(json_str distributor "$MANIFEST")
  DISTRIBUTOR_URL=$(json_str distributor_url "$MANIFEST")

  log "name=$PKG_NAME version=$VERSION type=$TYPE ($DISPLAY_NAME)"

  # appid
  cur=100000
  [ -f "$MAX_APPID_FILE" ] && cur=$(cat "$MAX_APPID_FILE" 2>/dev/null || echo 100000)
  dbmax=$(sqlite3 "$APPMARKET_DB" "select max(appid) from applist" 2>/dev/null || echo 0)
  [ "$dbmax" -gt "$cur" ] 2>/dev/null && cur=$dbmax
  APPID=$((cur + 1))
  echo "$APPID" > "$MAX_APPID_FILE" 2>/dev/null || true

  APP_PATH="$APPS_DIR/$PKG_NAME"
  log "installing..."

  # Stop existing
  if [ -x "$APP_PATH/.pkg/scripts/stop.sh" ]; then
    cd "$APP_PATH/.pkg/scripts"
    ./stop.sh >> "$APP_PATH/log/run.log" 2>&1 2>/dev/null || true
    cd /
  fi

  mkdir -p "$APP_PATH/.pkg" "$APP_PATH/log" "$APP_PATH/.tmp" "$APP_PATH/.backup"
  cp -rf "$SRC/"* "$APP_PATH/.pkg/" 2>/dev/null || err "copy failed"

  # Symlinks
  if [ -d "$APP_PATH/.pkg/app/config" ]; then
    ln -sf .pkg/app/config "$APP_PATH/config"
    touch "$APP_PATH/config/environment"
    [ ! -e "$APP_PATH/.pkg/app/environment" ] && touch "$APP_PATH/.pkg/app/environment"
  fi
  [ -d "$APP_PATH/.pkg/app/data" ] && ln -sf .pkg/app/data "$APP_PATH/data"
  [ -d "$APP_PATH/.pkg/app/cache" ] && ln -sf .pkg/app/cache "$APP_PATH/cache"

  mkdir -p "$APPMARKET_DIR"
  [ ! -e "$APPMARKET_DIR/$PKG_NAME" ] && ln -s "$APP_PATH" "$APPMARKET_DIR/$PKG_NAME"

  # Icons
  APP_ICO=""
  if [ -e "$APP_PATH/.pkg/ui/ico/app.png" ]; then
    mkdir -p "$IK_DIR/www/static/appmarket/$PKG_NAME/ico"
    cp -f "$APP_PATH/.pkg/ui/ico/app.png" "$IK_DIR/www/static/appmarket/$PKG_NAME/ico/"
    APP_ICO="/static/appmarket/$PKG_NAME/ico/app.png"
  fi
  if [ -d "$APP_PATH/.pkg/ui/images" ]; then
    mkdir -p "$IK_DIR/www/static/appmarket/$PKG_NAME/images"
    cp -rf "$APP_PATH/.pkg/ui/images/"* "$IK_DIR/www/static/appmarket/$PKG_NAME/images/" 2>/dev/null || true
  fi

  # Inject HOST_IP into .env — mirrors iKuai appmarket.sh logic:
  #   type=0 (native): HOST_IP = lan1 IP (e.g. 192.168.60.1)
  #   type=1 (docker): HOST_IP = doc_app_default gateway IP (e.g. 172.18.20.1)
  # The WebUI "open" button reads HOST_IP + APP_PORT_WEB to build the URL.
  # Without this the URL is http://:PORT/ (empty host → "invalid URL" error).
  ENV_FILE="$APP_PATH/.pkg/app/.env"
  [ -f "$ENV_FILE" ] || ENV_FILE="$APP_PATH/.pkg/app/environment"
  if [ -f "$ENV_FILE" ]; then
    if [ "$TYPE" = "1" ]; then
      # Docker: use doc_app_default bridge gateway
      HOST_IP_INJECT=$(ip -o -4 addr show doc_app_default 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)
    else
      # Native: use lan1 IP
      HOST_IP_INJECT=$(ip -o -4 addr show lan1 2>/dev/null | awk '{print $4}' | cut -d/ -f1 | head -1)
    fi
    # Fallback chain
    [ -z "$HOST_IP_INJECT" ] && HOST_IP_INJECT=$(sqlite3 /etc/mnt/ikuai/config.db "select ip_mask from lan_config limit 1" 2>/dev/null | cut -d/ -f1)
    [ -z "$HOST_IP_INJECT" ] && HOST_IP_INJECT=$(ip -o -4 addr show 2>/dev/null | grep -v '127.0.0.1' | awk '{print $4}' | cut -d/ -f1 | head -1)
    if [ -n "$HOST_IP_INJECT" ]; then
      # Append HOST_IP at end (sh source: last definition wins, overriding option.json default)
      # Remove any existing HOST_IP lines first, then append the resolved one.
      grep -v '^HOST_IP=' "$ENV_FILE" > "$ENV_FILE.tmp" 2>/dev/null && mv "$ENV_FILE.tmp" "$ENV_FILE"
      echo "HOST_IP=$HOST_IP_INJECT" >> "$ENV_FILE"
      log "injected HOST_IP=$HOST_IP_INJECT (type=$TYPE)"
    fi
  fi

  # Register in db
  TIMESTAMP=$(date +%s)
  DESC_B64=$(printf '%s' "$DESCRIPTION" | base64 | tr -d '\n')
  README=""
  [ -e "$APP_PATH/.pkg/readme" ] && README=$(cat "$APP_PATH/.pkg/readme" | base64 | tr -d '\n')
  CHANGELOG=""
  [ -e "$APP_PATH/.pkg/changelog" ] && CHANGELOG=$(cat "$APP_PATH/.pkg/changelog" | base64 | tr -d '\n')

  sqlite3 "$APPMARKET_DB" "DELETE FROM applist WHERE name='$PKG_NAME'" 2>/dev/null || true
  sqlite3 "$APPMARKET_DB" "INSERT INTO applist (
    enabled,name,appid,timestamp,display_name,location,ico,type,size,
    restart,current_version,maintainer,maintainer_url,distributor,distributor_url,
    origin,update_time,install_path,category,sub_category,desc,readme,changelog
  ) VALUES (
    'yes','$PKG_NAME',$APPID,$TIMESTAMP,'$DISPLAY_NAME','','$APP_ICO',$TYPE,0,
    0,'$VERSION','$MAINTAINER','$MAINTAINER_URL','$DISTRIBUTOR','$DISTRIBUTOR_URL',
    $NAIXI_ORIGIN,$TIMESTAMP,'$WORKDISK',7,0,'$DESC_B64','$README','$CHANGELOG'
  )" 2>/dev/null || err "failed to write appmarket.db"

  log "registered (appid=$APPID)"

  # Lifecycle
  SCRIPTS_DIR="$APP_PATH/.pkg/scripts"
  if [ -d "$SCRIPTS_DIR" ]; then
    cd "$SCRIPTS_DIR"
    [ -x "./PRE_INST.sh" ] && ./PRE_INST.sh >/dev/null 2>&1 || true
    log "starting..."
    if [ -x "./start.sh" ]; then
      # Background start.sh (like appmarket does) — some start.sh download
      # large images or run daemons foreground; we must not block.
      # iKuai busybox has no nohup/setsid, use subshell + disown pattern.
      ./start.sh >> "$APP_PATH/log/run.log" 2>&1 &
      START_PID=$!
      sleep 2
      if kill -0 "$START_PID" 2>/dev/null; then
        log "started in background (pid=$START_PID, log: $APP_PATH/log/run.log)"
      else
        wait "$START_PID" 2>/dev/null || true
        log "WARNING: start.sh exited early (check $APP_PATH/log/run.log)"
      fi
    else
      log "WARNING: no start.sh found"
    fi
    [ -x "./POST_INST.sh" ] && ./POST_INST.sh >/dev/null 2>&1 || true
  fi

  echo "0" > "$APP_PATH/log/run.status"
  log "done: $PKG_NAME v$VERSION (appid=$APPID)"
}

# ─── uninstall <name> ─────────────────────────────────────────────
do_uninstall() {
  NAME="$1"
  [ "$NAME" = "$SELF_NAME" ] && err "cannot uninstall naixi itself (use: rm -rf /etc/disk_user/*/apps/naixi)"
  APP_PATH=$(sqlite3 "$APPMARKET_DB" "SELECT '/etc/disk_user/'||install_path||'/apps/'||name FROM applist WHERE name='$NAME'" 2>/dev/null)
  [ -n "$APP_PATH" ] || err "app not found: $NAME"

  log "uninstalling $NAME..."
  SCRIPTS_DIR="$APP_PATH/.pkg/scripts"
  if [ -d "$SCRIPTS_DIR" ]; then
    cd "$SCRIPTS_DIR"
    [ -x "./PRE_UNINST.sh" ] && ./PRE_UNINST.sh >/dev/null 2>&1 || true
    [ -x "./stop.sh" ] && ./stop.sh >> "$APP_PATH/log/run.log" 2>&1 || true
    [ -x "./POST_UNINST.sh" ] && ./POST_UNINST.sh >/dev/null 2>&1 || true
  fi

  sqlite3 "$APPMARKET_DB" "DELETE FROM applist WHERE name='$NAME'" 2>/dev/null || true
  rm -rf "$APP_PATH" "/var/appmarket/$NAME" "$IK_DIR/www/static/appmarket/$NAME"
  log "done: $NAME removed"
}

# ─── list ─────────────────────────────────────────────────────────
do_list() {
  sqlite3 -header -column "$APPMARKET_DB" \
    "SELECT name,current_version,type,enabled,appid FROM applist ORDER BY name" 2>/dev/null \
    || echo "no apps"
}

# ─── main dispatch ────────────────────────────────────────────────
CMD="${1:-help}"
[ "$#" -gt 0 ] && shift || true

case "$CMD" in
  install)
    [ "$#" -ge 1 ] || err "usage: naixi install <file.ipkg|url> [-official]"
    do_install "$@"
    ;;
  uninstall|remove)
    [ "$#" -ge 1 ] || err "usage: naixi uninstall <name>"
    do_uninstall "$1"
    ;;
  list|ls)
    do_list
    ;;
  self-install)
    self_install
    ;;
  self-uninstall)
    self_uninstall
    ;;
  version|-v)
    echo "$NAIXI_VERSION"
    ;;
  update|self-update)
    log "downloading latest..."
    TMPFILE="/tmp/naixi-update-$$"
    curl -fsSL "$DL_BASE/naixi" -o "$TMPFILE" || err "download failed"
    mv "$TMPFILE" /usr/sbin/naixi
    chmod +x /usr/sbin/naixi
    self_install
    log "updated to $(naixi version)"
    ;;
  help|--help|-h)
    cat <<EOF
Naixi v$NAIXI_VERSION — iKuai app manager

Usage:
  naixi install <file.ipkg|url> [-official]    Install and start an app
  naixi uninstall <name>                        Remove an app
  naixi list                                    List installed apps
  naixi version                                 Show version
  naixi update                                  Update naixi itself
  naixi self-install                            Register naixi in appmarket
  naixi self-uninstall                          Remove naixi from appmarket

Options:
  -official    Mark as official (origin=0, default). Survives reboot.
  -sideload    Mark as sideload (origin=3). NOT auto-started on boot.

Install from URL:
  naixi install https://dl.naixi.net/ikuai/apps/anylink-1.2.0-amd64.ipkg
  naixi install https://dl.naixi.net/ikuai/apps/anylink-1.2.0-amd64.ipkg -official
EOF
    ;;
  *)
    err "unknown command: $CMD (try: naixi help)"
    ;;
esac
