#!/usr/bin/env bash
# Thin wrapper around `systemctl` / `journalctl` for the per-company
# FileShare storage server. Usable by the `fileshare` system user (via
# a NOPASSWD sudoers drop-in installed alongside this script), by root,
# or by any sudo-capable admin.
set -euo pipefail

UNIT="fileshare-company-server"
SYSTEMCTL="/usr/bin/systemctl"
JOURNALCTL="/usr/bin/journalctl"

INSTALL_DIR=/opt/fileshare_company_server
DATA_DIR=/var/lib/fileshare-company-server
CONFIG_DIR=/etc/fileshare-company-server
UNIT_FILE=/etc/systemd/system/${UNIT}.service
SUDOERS_FILE=/etc/sudoers.d/${UNIT}
WRAPPER_PATH=/usr/local/bin/fileshareSRV
SERVICE_USER=fileshare
SERVICE_GROUP=fileshare

usage() {
  cat <<USAGE
Usage: fileshareSRV [start|stop|restart|status|logs|info|code|reset|enable|disable|uninstall]

  (no args)  Smart status: prints the enrollment code if the server is
             waiting to pair, otherwise reports whether it's paired or
             stopped.
  info       Show this server's version, config, and enrollment details
             (server_id, who it's paired with, storage path). Read-only.
  start      Start the server now (then prints the code if pairing is
             needed)
  stop       Stop the server
  restart    Stop and start again (then prints the code if pairing is
             needed)
  status     Show raw systemd service status
  logs       Tail server logs (Ctrl-C to exit)
  code       Print the enrollment code (only once the server is ready to
             pair; errors out if the server is already paired)
  reset      Disassociate this server from its company (wipes credentials,
             keeps upload data). Restarts the service and prints the fresh
             enrollment code automatically. Pass --yes to skip the
             confirmation prompt.
  enable     Start automatically on boot
  disable    Do not start automatically on boot
  uninstall  Remove fileshare-company-server from this machine
             (add --purge to also wipe customer data)
USAGE
}

# The enrollment-code lifecycle is observed by tailing the journal. The
# interesting log lines are:
#
#   Listening on http://...          → server is paired and running
#   Switching to normal mode         → same; we already have credentials
#   Opening claim WS: ...            → claim WS is connected to central,
#                                      the printed code is hot
#   wiping local credentials         → central disowned us; we'll re-claim
#   ENROLLMENT CODE: XXXX-YYYY       → the code itself
#   Code valid for N minutes.        → printed alongside the code
#
# We only surface the code once "Opening claim WS" is the latest state
# line — surfacing it earlier means central hasn't acknowledged the code
# yet and pairing from the app fails with "code not found".

# Print the `--since` args needed so journalctl only shows lines from the
# current service invocation. Stale codes from previous boots would
# otherwise leak through.
journal_since_args() {
  local active_since
  active_since=$("$SYSTEMCTL" show -p ActiveEnterTimestamp --value "$UNIT" 2>/dev/null || true)
  if [ -n "${active_since:-}" ] \
     && [ "$active_since" != "n/a" ] \
     && [ "$active_since" != "0" ]; then
    printf -- '--since=%s\n' "$active_since"
  fi
}

# Polls the journal and prints "claim" / "listening" / "" depending on
# whether the server is currently waiting to pair, already paired, or
# neither (still starting up). Caller is responsible for the timeout.
detect_pair_state() {
  local since_args=()
  local s
  while IFS= read -r s; do since_args+=("$s"); done < <(journal_since_args)

  local log
  log=$(sudo -n "$JOURNALCTL" -u "$UNIT" "${since_args[@]}" --no-pager -o cat 2>/dev/null || true)

  local state
  state=$(printf '%s\n' "$log" \
          | grep -E "Opening claim WS|Listening on http" \
          | tail -1 || true)

  case "$state" in
    *"Listening on http"*) echo "listening" ;;
    *"Opening claim WS"*)  echo "claim" ;;
    *)                     echo "" ;;
  esac
}

# Print the enrollment code lines from the current invocation's log.
print_code_lines() {
  local since_args=()
  local s
  while IFS= read -r s; do since_args+=("$s"); done < <(journal_since_args)
  sudo -n "$JOURNALCTL" -u "$UNIT" "${since_args[@]}" --no-pager -o cat 2>/dev/null \
    | grep -E "ENROLLMENT CODE|Code valid" \
    | tail -4 || true
}

cmd_code() {
  if ! "$SYSTEMCTL" is-active --quiet "$UNIT"; then
    echo "fileshare-company-server is not running. Start it with:" >&2
    echo "  fileshareSRV start" >&2
    exit 1
  fi

  local deadline=$(( $(date +%s) + 15 ))
  while : ; do
    case "$(detect_pair_state)" in
      listening)
        echo "Server is already paired with the FileShare app — no enrollment code is active." >&2
        echo "To re-pair, disconnect this server in the app first, then run:" >&2
        echo "  fileshareSRV reset" >&2
        exit 1
        ;;
      claim)
        print_code_lines
        exit 0
        ;;
    esac

    if [ "$(date +%s)" -ge "$deadline" ]; then
      echo "Server is starting but isn't ready to accept pairing yet." >&2
      echo "Wait a few seconds and retry, or inspect:" >&2
      echo "  fileshareSRV logs" >&2
      exit 1
    fi
    sleep 1
  done
}

# Smart default: pick whichever of {code, paired status, stopped} matches
# the current state. This is what bare `fileshareSRV` runs and what
# `start` / `restart` / `reset` chain into so the user doesn't need a
# follow-up command to see the code.
cmd_auto() {
  if ! "$SYSTEMCTL" is-active --quiet "$UNIT"; then
    echo "fileshare-company-server is not running."
    echo "Start it with:  fileshareSRV start"
    return 0
  fi

  local deadline=$(( $(date +%s) + 20 ))
  while : ; do
    case "$(detect_pair_state)" in
      listening)
        echo "fileshare-company-server is paired and running."
        echo "(Run 'fileshareSRV status' for service details, or"
        echo " 'fileshareSRV reset' to disconnect and pair again.)"
        return 0
        ;;
      claim)
        echo "Server is waiting to be paired. Enrollment code:"
        echo ""
        print_code_lines
        return 0
        ;;
    esac

    if [ "$(date +%s)" -ge "$deadline" ]; then
      echo "Server is starting but isn't ready yet."
      echo "Retry in a few seconds, or inspect:  fileshareSRV logs"
      return 0
    fi
    sleep 1
  done
}

cmd_reset() {
  shift || true
  local yes=0
  local a
  for a in "$@"; do
    case "$a" in
      -y|--yes) yes=1 ;;
      *) echo "Unknown flag: $a" >&2; exit 64 ;;
    esac
  done

  if [ "$yes" -ne 1 ]; then
    echo "This will disassociate this server from its company:"
    echo "  - credentials.json and central_public.pem will be deleted"
    echo "  - upload chunks and the state DB will be preserved"
    echo "  - the service will be restarted and print a fresh enrollment code"
    echo ""
    local answer=""
    printf "Are you sure? [yes/no] "
    read -r answer || answer=""
    case "${answer}" in
      y|Y|yes|YES|Yes) ;;
      *) echo "Aborted."; exit 0 ;;
    esac
  fi

  # Stop the service first so the wiped credentials aren't still cached in
  # the running daemon's memory. The daemon would otherwise keep posting
  # signed webhooks for a company it no longer belongs to until the next
  # restart.
  echo "→ stopping ${UNIT}"
  sudo -n "$SYSTEMCTL" stop "$UNIT"

  echo "→ wiping credentials"
  # If we got here as root (e.g. via `sudo fileshareSRV reset`), drop to
  # the service user so any files the binary touches stay owned by
  # ${SERVICE_USER}. When invoked directly by the fileshare user this is
  # a no-op.
  local runner=()
  if [ "$(id -u)" -eq 0 ]; then
    runner=(runuser -u "$SERVICE_USER" --)
  fi
  # Force STORAGE_PATH to the service's data dir. `runuser` does NOT carry
  # the systemd unit's Environment=STORAGE_PATH, and server.env may leave it
  # unset — without this, reset defaults to ~/.config/... and wipes the wrong
  # (empty) directory while the service keeps its credentials in DATA_DIR.
  "${runner[@]}" \
    env STORAGE_PATH="${DATA_DIR}" \
    "${INSTALL_DIR}/fileshare_company_server" reset --yes \
    --env="${CONFIG_DIR}/server.env"

  echo "→ starting ${UNIT}"
  sudo -n "$SYSTEMCTL" start "$UNIT"

  echo ""
  echo "Waiting for fresh enrollment code..."
  echo ""
  cmd_auto
}

cmd_info() {
  # Read-only — no systemctl needed. credentials.json is mode 0600 owned by
  # the service user, so when invoked as root drop to that user to read it
  # (mirrors cmd_reset). When the fileshare user runs this directly, it reads
  # its own files and the runner is a no-op.
  local runner=()
  if [ "$(id -u)" -eq 0 ]; then
    runner=(runuser -u "$SERVICE_USER" --)
  fi
  # Same STORAGE_PATH pinning as cmd_reset so `info` reads the credentials the
  # service actually uses, not the ~/.config default.
  "${runner[@]}" \
    env STORAGE_PATH="${DATA_DIR}" \
    "${INSTALL_DIR}/fileshare_company_server" info \
    --env="${CONFIG_DIR}/server.env"
}

cmd_uninstall() {
  shift || true
  local purge=0
  local a
  for a in "$@"; do
    case "$a" in
      --purge) purge=1 ;;
      *) echo "Unknown flag: $a" >&2; exit 64 ;;
    esac
  done

  if [ "$(id -u)" -ne 0 ]; then
    echo "fileshareSRV uninstall must be run as root, e.g.:" >&2
    echo "  sudo fileshareSRV uninstall" >&2
    exit 1
  fi

  echo "This will stop and remove fileshare-company-server from this machine."
  if [ "$purge" -eq 1 ]; then
    echo "Customer data in ${DATA_DIR} and config in ${CONFIG_DIR}"
    echo "will be DELETED, and the '${SERVICE_USER}' system user removed."
  else
    echo "Customer data in ${DATA_DIR} and config in ${CONFIG_DIR}"
    echo "will be PRESERVED. (Pass --purge to also wipe them.)"
  fi
  echo ""

  local answer=""
  printf "Are you sure? [yes/no] "
  read -r answer || answer=""
  case "${answer}" in
    y|Y|yes|YES|Yes) ;;
    *) echo "Aborted."; exit 0 ;;
  esac

  # If installed via dpkg/apt, route through the package manager so its
  # database stays consistent — otherwise apt will still think the
  # package is installed and refuse later upgrades.
  if command -v dpkg-query >/dev/null 2>&1 \
     && dpkg-query -W -f='${Status}' fileshare-company-server 2>/dev/null \
        | grep -q "install ok installed"; then
    if [ "$purge" -eq 1 ]; then
      apt-get -y purge fileshare-company-server
    else
      apt-get -y remove fileshare-company-server
    fi
    echo "Removed."
    exit 0
  fi

  # Manual install layout — mirrors the self-extracting installer's
  # do_uninstall so the two paths stay in sync.
  echo "Stopping service..."
  "$SYSTEMCTL" stop    "${UNIT}.service" >/dev/null 2>&1 || true
  "$SYSTEMCTL" disable "${UNIT}.service" >/dev/null 2>&1 || true

  rm -f "$UNIT_FILE" "$SUDOERS_FILE"
  rm -rf "$INSTALL_DIR"

  if [ "$purge" -eq 1 ]; then
    rm -rf "$DATA_DIR" "$CONFIG_DIR"
    if getent passwd "$SERVICE_USER" >/dev/null; then
      userdel "$SERVICE_USER" >/dev/null 2>&1 || true
    fi
    if getent group "$SERVICE_GROUP" >/dev/null; then
      groupdel "$SERVICE_GROUP" >/dev/null 2>&1 || true
    fi
  fi

  if [ -d /run/systemd/system ]; then
    "$SYSTEMCTL" daemon-reload >/dev/null 2>&1 || true
  fi

  # Removing the wrapper while it's executing is safe on Linux: bash has
  # already loaded the file, and unlink doesn't truncate the inode until
  # the last fd closes.
  rm -f "$WRAPPER_PATH"

  echo "Removed."
  if [ "$purge" -ne 1 ]; then
    echo "Customer data left in ${DATA_DIR} (re-run 'uninstall --purge' to wipe)."
  fi
}

cmd="${1:-}"
case "$cmd" in
  "")
    cmd_auto
    ;;
  start|restart)
    sudo -n "$SYSTEMCTL" "$cmd" "$UNIT"
    echo ""
    cmd_auto
    ;;
  stop|reload|enable|disable)
    exec sudo -n "$SYSTEMCTL" "$cmd" "$UNIT"
    ;;
  status)
    exec "$SYSTEMCTL" status "$UNIT" --no-pager
    ;;
  logs)
    exec sudo -n "$JOURNALCTL" -u "$UNIT" -f
    ;;
  info)
    cmd_info
    ;;
  code)
    cmd_code
    ;;
  reset)
    cmd_reset "$@"
    ;;
  uninstall)
    cmd_uninstall "$@"
    ;;
  -h|--help|help)
    usage
    ;;
  *)
    usage >&2
    exit 64
    ;;
esac
