#!/bin/bash

PKG="unifi-protect"
USER="unifi-protect"
GROUP="unifi-streaming"

DB_USER="unifi-protect"
DB_PORT=5433
LOGS_DIR="/var/log/$PKG"
EXTERNAL_MINIMUM_SPACE=54683238 # kibibyte = 52.15 GiB = 56 GB
COUNTER="/tmp/.protect.cnt"
RUN_DIR="/var/run/unifi-protect"

if [ -z $UFP_INTERNAL_DIR ] && [ -z $UFP_EXTERNAL_DIR ]; then
  echo "############## FAILED! neither UFP_INTERNAL_DIR or UFP_EXTERNAL_DIR is set, exiting #########" 1>&2
  exit 1
fi

# Create DB user
su postgres -c "createuser $DB_USER -p $DB_PORT -d" || true

usermod -g $GROUP $USER

is_link() {
  [ -L "$1" ] && [ -e "$1" ] && return
  false
}

get_disk_space() {
  df "$1" -k | tail -n 1 | awk '{printf $2}'
}

# clean up ems log if too big
clean_up_oversized_log() {
  local dir=$1
  local log_path="$dir/logs"

  [ -d $log_path ] && find "$log_path" -name "*.log" -type f -size +20M -exec rm -f '{}' \;
}

saveJournals() {
  # Check if journalctl is available
  if ! command -v journalctl &> /dev/null; then
    echo "journalctl command not found. Make sure you have systemd installed."
    return 1
  fi

  unit_name="$PKG.service"
  log_file="/tmp/.lastProtectJournal"
  alljournal_file="/tmp/journal-all"
  journalctl -u "$unit_name" --since "1 minute ago" > "$log_file"
  journalctl --all --since '1 hours ago' > $alljournal_file
  chown $USER:$GROUP $log_file $alljournal_file

  echo "Journal log saved to $log_file and $alljournal_file"
}

isSrvReadOnly() {
  if ! command -v findmnt &> /dev/null; then
    # ignore if findmnt not found
    return 1
  fi
  # report RO state only if target mounted
  echo "Read Only file system check"
  local TMP=$(findmnt --target /srv -o SOURCE,OPTIONS -n)
  echo $TMP
  echo $TMP | grep -E '^ro$|,ro$|,ro,|^ro,| ro'
  return $?
}

# chown only when the current owner:group differs from the desired one.
# chown() always rewrites the inode (bumping ctime), so skipping the syscall
# on already-correct entries is a real I/O saving on large trees.
chown_if_needed() {
  local spec=$1 target=$2
  [ -e "$target" ] || return 0
  local current
  if current=$(stat -L -c '%U:%G' "$target" 2>/dev/null); then
    [ "$current" = "$spec" ] && return 0
  fi
  chown "$spec" "$target"
}

# Same as chown_if_needed but for the symlink itself (no dereference).
chown_link_if_needed() {
  local spec=$1 target=$2
  [ -L "$target" ] || return 0
  local current
  if current=$(stat -c '%U:%G' "$target" 2>/dev/null); then
    [ "$current" = "$spec" ] && return 0
  fi
  chown -h "$spec" "$target"
}

# Recursive chown that touches only entries whose owner or group does not
# already match. Spec must be "user:group" — group-only paths (UFP_VIDEO_DIR,
# UFP_EXPORTS_DIR, extension-unit video dirs) stay on inline
# `find ! -group … -exec chown :$GROUP` since they already have the predicate.
# `-c` plus a tailing pipe preserves a short trace of which entries actually
# changed; with multi-target calls the tail is aggregate across all trees, not
# per-tree like the previous `chown -c -R … | tail` was.
chown_r_if_needed() {
  local spec=$1
  shift
  local u="${spec%%:*}"
  local g="${spec#*:}"
  local t
  for t in "$@"; do
    [ -e "$t" ] || continue
    find "$t" '(' '!' -user "$u" -o '!' -group "$g" ')' -exec chown -c -h "$spec" {} + 2>/dev/null
  done | tail -n 10
}

run_rescue() {
  rescueDir="/usr/share/unifi-protect/app/hooks/rescue/"

  # Find all scripts in the rescue directory, sorted by date in their filename
  for script in $(ls "$rescueDir" | grep -E '^[0-9]{8}-.+\.sh$' | sort); do
    full_script_path="$rescueDir/$script"

    if [ -x "$full_script_path" ]; then
      $full_script_path
    fi
  done
}

# Adding this run's timestamp to the counter file
# Truncating the counter file to the last 8 timestamps max
update_restart_counter() {
  date +%s >> $COUNTER
  echo "$(tail -n8 $COUNTER)" > $COUNTER
  chown $USER:$GROUP $COUNTER 2>&1 >/dev/null
}

saveJournals

# Safety net: if PostgreSQL is running with /data/ but /srv/ has the real DB,
# run the migrate script to fix config and restart PG. This catches race
# conditions where PG started before /srv was mounted (FW upgrade, HDD insertion).
# /srv/ is usually mounted by the time pre-start runs, but RAID mount can be
# delayed (UOS firmware allows apps to start before mount completes). The
# late-mount case is handled by the migrate service itself, which runs after
# /srv/ comes online; this hook is the safety net for the case where PG was
# already running on /data/ before that service got a chance to restart it.
#
# Skip on consoles with built-in SSD — DB runs from /data/ by design there, and
# /srv/ may contain a leftover DB (e.g. HDD raid moved from a non-SSD console)
# that must NOT be migrated into /data/.
PG_VERSION=14
PG_CLUSTER_NAME=protect
INTERNAL_DB_ROOT=/data/postgresql
EXTERNAL_DB_DIR="/srv/postgresql/$PG_VERSION/$PG_CLUSTER_NAME"
INTERNAL_DB_DATADIR="/data/postgresql/$PG_VERSION/$PG_CLUSTER_NAME/data"
if ! (is_link $INTERNAL_DB_ROOT || mountpoint -q /ssd1 || [ -d /ssd1 ]) \
  && pg_isready -p $DB_PORT -q 2>/dev/null \
  && [ -f "$EXTERNAL_DB_DIR/.configured" ] && [ -d "$EXTERNAL_DB_DIR/data" ]; then
  running_dir="$(su postgres -c "psql -p $DB_PORT -tAc 'SHOW data_directory'" 2>/dev/null | tr -d '[:space:]')"
  if [ "$running_dir" = "$INTERNAL_DB_DATADIR" ]; then
    echo "pre-start: PostgreSQL running with /data/ but /srv/ has the real DB, running migrate script" 1>&2
    /usr/bin/unifi-protect-db-cluster-migrate 2>&1 || true
  fi
fi

# Internal directory most exist! Fallback path requires it to create logger
if [ ! -d $UFP_INTERNAL_DIR ]; then
  # Create internal directory
  mkdir $UFP_INTERNAL_DIR
  chown $USER:$GROUP $UFP_INTERNAL_DIR
fi

if [ -d $UFP_INTERNAL_DIR ]; then
  UFP_VIDEO_DIR="${UFP_INTERNAL_DIR}/video"
  UFP_EXPORTS_DIR="${UFP_INTERNAL_DIR}/exports"
fi

# External storage available
if (mountpoint -q /srv || is_link /srv) && [ $(get_disk_space /srv) -ge $EXTERNAL_MINIMUM_SPACE ] && (! isSrvReadOnly); then
  if [ ! -d $UFP_EXTERNAL_DIR ]; then
    # Create external directory
    mkdir $UFP_EXTERNAL_DIR
    chown $USER:$GROUP $UFP_EXTERNAL_DIR
  fi

  UFP_VIDEO_DIR="${UFP_EXTERNAL_DIR}/video"
  UFP_EXPORTS_DIR="${UFP_EXTERNAL_DIR}/exports"

  if [ "$(find ${UFP_INTERNAL_DIR}/ -maxdepth 1 -mindepth 1 -type d -not -name 'logs' -not -name 'video' | wc -l)" -gt 0 ] \
    || [ "$(find ${UFP_INTERNAL_DIR}/video -type f -not -name '*_thumbnails_*.ubv' -print -quit 2>/dev/null)" ]; then
    # Extend systemd start timeout for internal→external data migration.
    # Default TimeoutStartSec (300s) is insufficient when stopping media services (~90s)
    # plus rsync on large datasets or degraded RAID can exceed the remaining budget.
    # 900s (15 min) covers worst-case: service stops + rsync + DB path updates.
    systemd-notify "EXTEND_TIMEOUT_USEC=900000000" 2>/dev/null || true

    systemctl stop ms 2>/dev/null || true
    systemctl stop msr 2>/dev/null || true
    systemctl stop msp 2>/dev/null || true
    systemctl stop mst 2>/dev/null || true
    systemctl stop ds 2>/dev/null || true

    # Unmount temp filesystem from internal directory, ignore if not exists
    umount -l $UFP_INTERNAL_DIR/temp 2>/dev/null || true

    # Move everything to external directory except logs and thumbnail UBV files.
    # Thumbnail recordings should remain on internal storage (SSD) for fast access.
    rsync --exclude 'logs' --exclude '*_thumbnails_*.ubv' -a $UFP_INTERNAL_DIR/ $UFP_EXTERNAL_DIR/

    # Update DB records, due to we support motion only recording if NVR has buildIn SSD.
    # Skip thumbnail recordings - they remain on internal storage.
    psql -U $DB_USER -p $DB_PORT -c "UPDATE \"recordingFiles\" SET folder = REPLACE(folder, '/data/unifi-protect', '/srv/unifi-protect') WHERE type IS DISTINCT FROM 'thumbnails'"
    psql -U $DB_USER -p $DB_PORT -c "UPDATE \"backupFiles\" SET path = REPLACE(path, '/data/unifi-protect', '/srv/unifi-protect')"
    psql -U $DB_USER -p $DB_PORT -c "UPDATE \"updates\" SET path = REPLACE(path, '/data/unifi-protect', '/srv/unifi-protect')"

    # Delete data on internal directory except logs and thumbnail UBV files.
    # First remove all non-video, non-logs top-level directories
    find "${UFP_INTERNAL_DIR}" -mindepth 1 -maxdepth 1 -not -name 'logs' -not -name 'video' -exec rm -rf {} +
    # Then remove non-thumbnail files from video directory
    find "${UFP_INTERNAL_DIR}/video" -type f -not -name '*_thumbnails_*.ubv' -delete 2>/dev/null || true
    # Clean up empty directories in video, preserving directory structure for remaining thumbnails
    find "${UFP_INTERNAL_DIR}/video" -mindepth 1 -type d -empty -delete 2>/dev/null || true
    # After moving data to external, restart MS
    systemctl --no-block start ms 2>/dev/null || true
    systemctl --no-block start msr 2>/dev/null || true
    systemctl --no-block start msp 2>/dev/null || true
    systemctl --no-block start mst 2>/dev/null || true
    systemctl --no-block start ds 2>/dev/null || true
  fi

  # Check disk has enough free spaces to start Protect
  free_spaces=$(df -k /srv | tail -n 1 | awk '{printf $4}')
  # disk quota hard limit is 16G, set the MIN_SPACES to 20 GB
  MIN_SPACES=20971520
  GB=1048576
  if [ "${free_spaces}" -lt "${MIN_SPACES}" ]; then
    echo "Not enough free spaces to start Protect, free spaces: ${free_spaces} KB, required: ${MIN_SPACES} KB"
    du -h -d 1 /srv/

    num=$(((MIN_SPACES - free_spaces) / GB + 1))

    [ -d $UFP_VIDEO_DIR ] && find $UFP_VIDEO_DIR -type f -printf '%T+ %p\n' | grep '0_rotating' | sort | head -n ${num} | awk '{print $2}' | xargs rm -vf

    echo "Free up ${num} GB spaces, now free spaces: $(df -k /srv | tail -n 1 | awk '{printf $4}') KB"
  fi
fi

# Support directories
mkdir -p $UFP_BACKUPS_DIR $UFP_JSONDB_DIR $LOGS_DIR $RUN_DIR
chown_r_if_needed "$USER:$GROUP" "$UFP_BACKUPS_DIR" "$UFP_JSONDB_DIR" "$LOGS_DIR" "$RUN_DIR"

if [ -d $UFP_INTERNAL_DIR ]; then
  echo "############################ Setting [$USER:$GROUP] ownership on UFP_INTERNAL_DIR=[${UFP_INTERNAL_DIR}] ###########################" 1>&2
  clean_up_oversized_log $UFP_INTERNAL_DIR

  if [ -L $UFP_INTERNAL_DIR ]; then
    chown_link_if_needed "$USER:$GROUP" "$UFP_INTERNAL_DIR"
    chown_r_if_needed "$USER:$GROUP" "$UFP_INTERNAL_DIR/"
  else
    chown_r_if_needed "$USER:$GROUP" "$UFP_INTERNAL_DIR"
  fi
fi

if [ -d $UFP_EXTERNAL_DIR ]; then
  echo "############################# Setting [$USER:$GROUP] ownership on UFP_EXTERNAL_DIR=[${UFP_EXTERNAL_DIR}] #######################" 1>&2
  clean_up_oversized_log $UFP_EXTERNAL_DIR
  chown_if_needed "$USER:$GROUP" "$UFP_EXTERNAL_DIR"
  cv_path="$UFP_EXTERNAL_DIR/cv"
  while IFS= read -r -d '' dir; do
    chown_r_if_needed "$USER:$GROUP" "$dir"
  done < <(find "$UFP_EXTERNAL_DIR" -mindepth 1 -maxdepth 1 -type d -not -path "$cv_path" -not -path "$UFP_VIDEO_DIR" -print0)
  [ -d "$cv_path" ] && find "$cv_path" -maxdepth 1 -type d \( ! -user "$USER" -o ! -group "$GROUP" \) -exec chown -c $USER:$GROUP {} +
fi

mkdir -p $UFP_VIDEO_DIR || true

if [ -d $UFP_VIDEO_DIR ]; then
  chmod g+rw $UFP_VIDEO_DIR || true
  chown :$GROUP $UFP_VIDEO_DIR || true
  echo "############################# Setting group ${GROUP} ownership and permissions on ${UFP_VIDEO_DIR} ##############################" 1>&2
  # This is faster than changing permissions for all files
  find $UFP_VIDEO_DIR ! -perm -g=w -exec chmod g+rw {} + || true
  find $UFP_VIDEO_DIR ! -group "$GROUP" -exec chown :$GROUP {} + || true
  echo "############################# Setting group ${GROUP} ownership and permissions on ${UFP_VIDEO_DIR} OK ##########################" 1>&2
else
  echo "############################ FAILED! Could not set-up UFP_VIDEO_DIR=${UFP_VIDEO_DIR} exiting ##########################" 1>&2
  exit 1
fi

mkdir -p $UFP_EXPORTS_DIR || true

if [ -d $UFP_EXPORTS_DIR ]; then
  chmod g+rw $UFP_EXPORTS_DIR || true
  chown :$GROUP $UFP_EXPORTS_DIR || true
  echo "############################# Setting group ${GROUP} ownership and permissions on ${UFP_EXPORTS_DIR} ##############################" 1>&2
  # This is faster than changing permissions for all files
  find $UFP_EXPORTS_DIR ! -perm -g=w -exec chmod g+rw {} + || true
  find $UFP_EXPORTS_DIR ! -group "$GROUP" -exec chown :$GROUP {} + || true
  echo "############################# Setting group ${GROUP} ownership and permissions on ${UFP_EXPORTS_DIR} OK ##########################" 1>&2
else
  echo "############################ FAILED! Could not set-up UFP_EXPORTS_DIR=${UFP_EXPORTS_DIR} exiting ##########################" 1>&2
  exit 1
fi

echo "########## Video path UFP_VIDEO_DIR=[${UFP_VIDEO_DIR}] #########" 1>&2

# protect won't start on $UFP_EXTERNAL_DIR
if isSrvReadOnly; then
  touch /etc/$PKG/jsonDb/.hdd_corrupted
fi

# Get extension unit volume UUIDs from volume flag files
# Outputs: one UUID per line to stdout
get_extension_unit_volumes() {
  local JSONDB_DIR="/etc/$PKG/jsonDb"

  for flag_file in "$JSONDB_DIR"/.volume_*_was_inserted; do
    [ -e "$flag_file" ] || continue

    local filename=$(basename "$flag_file")
    local uuid=$(echo "$filename" | sed -n 's/^\.volume_\(.*\)_was_inserted$/\1/p')
    [ -n "$uuid" ] && echo "$uuid"
  done
}

# Setup video directories for extension units
# Reads UUIDs from stdin, creates /volume/$UUID/unifi-protect/video dirs, restarts MSR if needed
setup_extension_unit_video_dirs() {
  local RESTART_MSR=false

  while read -r uuid; do
    [ -z "$uuid" ] && continue

    local volume_path="/volume/${uuid}"
    local video_dir="${volume_path}/unifi-protect/video"

    if ! (mountpoint -q "$volume_path" 2>/dev/null || is_link "$volume_path"); then
      echo "Skipping preparing video dir for unmounted volume $uuid" 1>&2
      continue
    fi

    if [ ! -d "$video_dir" ]; then
      echo "Creating video directory for volume $uuid: $video_dir" 1>&2
      mkdir -p "$video_dir" || continue
      chown "$USER:$GROUP" "${volume_path}/unifi-protect" || true
      chmod g+rw "$video_dir" || true
      chown "$USER:$GROUP" "$video_dir" || true
      RESTART_MSR=true
    fi

    # Fix permissions on existing files
    find "$video_dir" ! -perm -g=w -exec chmod g+rw {} + 2>/dev/null || true
    find "$video_dir" ! -group "$GROUP" -exec chown :$GROUP {} + 2>/dev/null || true
  done

  if [ "$RESTART_MSR" = true ]; then
    echo "Restarting MSR after creating extension unit video directories" 1>&2
    systemctl restart msr 2>/dev/null || true
  fi
}

get_extension_unit_volumes | setup_extension_unit_video_dirs

update_certificates_access() {
  chmod a+r /data/unifi-core/config/unifi-core.crt || true
  chmod a+r /data/unifi-core/config/unifi-core.key || true
}

run_rescue
update_restart_counter
update_certificates_access
