#!/usr/bin/env bash
# Production: rotate live logs, curl -T to pog-atlas.com:51709 under Enrollment Path/Logs/
# (or /orphan/{asset}/Logs/ if the asset is set but not enrolled). Delete local archives
# only after confirm. Never deletes the live tail. Caps local archives.
#
# Dest is Enrollment.csv Path for config.ini asset_number — NOT hostname, NOT FIXTURE_POS,
# NOT Dropbox EtherGold/logs/N (that is install_sync.sh fixture capture).
# rclone is not the log client. Enable/disable is Identity /config (log_upload_enabled).
#
# Names: {asset}-{UTC}-{product}-{live-basename}.gz
# Example: 50003-20260825T170105Z-dyzineframe-log.txt.gz
#
#   bash log_offload.sh
#   LOG_OFFLOAD_FORCE=1 bash log_offload.sh     # close even if under rotate size
# 51709 WebDAV PUT needs IIS Basic auth (user SGVIUpload). Copy wput.env.example
# to ~/EtherGold/sync/wput.env on the Pi (chmod 600). Do not put the password in git.

if grep -q $'\r' "${BASH_SOURCE[0]}" 2>/dev/null; then
  sed -i 's/\r$//' "${BASH_SOURCE[0]}"
  exec bash "${BASH_SOURCE[0]}" "$@"
fi

set -euo pipefail

if [[ "$(id -un)" == root ]]; then
  echo "FATAL: do not run as root/sudo." >&2
  exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PRODUCT="${LOG_OFFLOAD_PRODUCT:-ethergold}"
ROTATE_BYTES="${LOG_ROTATE_BYTES:-8388608}"
ARCHIVE_CAP_BYTES="${LOG_ARCHIVE_CAP_BYTES:-52428800}"
FORCE="${LOG_OFFLOAD_FORCE:-0}"

if [[ "${PRODUCT}" == "ethergold" ]]; then
  LIVE_DIR="${HOME}/EtherGold/logs"
  LIVE_FILES=("supervisor.log" "errors.jsonl")
  ARCHIVE_DIR="${HOME}/EtherGold/logs/archive"
else
  LIVE_DIR="${HOME}/DyzineFrame/log"
  LIVE_FILES=("log.txt" "supervisor.log" "stage.log" "promote.log")
  ARCHIVE_DIR="${HOME}/DyzineFrame/log/archive"
fi

mkdir -p "${ARCHIVE_DIR}" "${LIVE_DIR}"

# Recurring path: User=topbox drop-in + chown logs; restart collector only if
# it is still root. Same script Promote runs. Idempotent.
if [[ -f "${HOME}/EtherGold/sync/install_uartd_persist_dropin.sh" ]]; then
  bash "${HOME}/EtherGold/sync/install_uartd_persist_dropin.sh" || echo "log_offload: WARN collector topbox drop-in not applied"
fi

for envf in "${HOME}/EtherGold/sync/wput.env" "${HOME}/DyzineFrame/sync/wput.env"; do
  if [[ -f "${envf}" ]]; then
    # shellcheck source=/dev/null
    source "${envf}"
    break
  fi
done

stamp="$(date -u +%Y%m%dT%H%M%SZ)"
host="$(hostname -s 2>/dev/null || hostname || echo pi)"

MODE="skip"
ASSET=""
ENROLLMENT_PATH=""
HTTPS_DEST=""
FILE_PREFIX=""
LOG_DEST_KIND=""
LOG_DEST_ERROR=""

RESOLVER=""
for cand in \
  "${HOME}/DyzineFrame/production_log_dest.py" \
  "${SCRIPT_DIR}/../production_log_dest.py" \
  "${SCRIPT_DIR}/production_log_dest.py"
do
  if [[ -f "${cand}" ]]; then
    RESOLVER="${cand}"
    break
  fi
done

if [[ -n "${RESOLVER}" ]] && command -v python3 >/dev/null 2>&1; then
  # shellcheck disable=SC1090
  eval "$(
    python3 "${RESOLVER}" --export-env --product "${PRODUCT}" || true
  )"
else
  LOG_DEST_ERROR="production_log_dest.py or python3 missing"
fi

if [[ -n "${FILE_PREFIX}" ]]; then
  NAME_PREFIX="${FILE_PREFIX}"
else
  NAME_PREFIX="${host}"
  echo "log_offload: no asset — local archives use hostname; skip IIS"
fi
if [[ -n "${LOG_DEST_ERROR}" ]]; then
  echo "log_offload: ${LOG_DEST_ERROR}"
fi

rotate_one() {
  local live="$1"
  [[ -f "${live}" ]] || return 0
  if [[ ! -r "${live}" || ! -w "${live}" ]]; then
    echo "log_offload: skip unreadable ${live}"
    return 0
  fi
  local size
  size="$(wc -c < "${live}" | tr -d ' ')"
  if [[ "${FORCE}" != "1" ]] && [[ "${size}" -lt "${ROTATE_BYTES}" ]]; then
    return 0
  fi
  if [[ "${size}" -le 0 ]]; then
    return 0
  fi
  local base dest
  base="$(basename "${live}")"
  dest="${ARCHIVE_DIR}/${NAME_PREFIX}-${stamp}-${PRODUCT}-${base}"
  if ! cp -a "${live}" "${dest}"; then
    echo "WARN: cannot copy ${live} — skip"
    return 0
  fi
  if ! : > "${live}"; then
    echo "WARN: cannot truncate ${live} — keeping copy ${dest}"
    return 0
  fi
  if command -v gzip >/dev/null 2>&1; then
    gzip -f "${dest}"
    dest="${dest}.gz"
  fi
  echo "rotated ${live} -> ${dest}"
}

for name in "${LIVE_FILES[@]}"; do
  rotate_one "${LIVE_DIR}/${name}"
done

confirm_put() {
  local url="$1"
  local local_file="$2"
  local local_len remote_len code
  local_len="$(wc -c < "${local_file}" | tr -d ' ')"
  if command -v curl >/dev/null 2>&1; then
    remote_len="$(curl -skI --max-time 30 "${url}" 2>/dev/null | awk 'BEGIN{IGNORECASE=1} /^content-length:/ {print $2}' | tr -d '\r' | tail -n 1)"
    if [[ -n "${remote_len}" ]] && [[ "${remote_len}" == "${local_len}" ]]; then
      return 0
    fi
    code="$(curl -sk -o /dev/null -w "%{http_code}" --max-time 30 "${url}" 2>/dev/null || true)"
    if [[ "${code}" == "200" ]]; then
      return 0
    fi
    return 1
  fi
  return 0
}

ensure_remote_folder() {
  local dest="$1"
  [[ -n "${dest}" ]] || return 0
  command -v curl >/dev/null 2>&1 || return 0
  local auth=()
  if [[ -n "${WPUT_USER:-}" ]]; then
    auth=(-u "${WPUT_USER}:${WPUT_PASSWORD:-}")
  fi
  curl -sk --max-time 15 -X MKCOL "${auth[@]}" "${dest}" >/dev/null 2>&1 || true
}

put_one() {
  local local_file="$1"
  local dest_url="$2"
  local code
  code="$(
    curl -sk -o /dev/null -w "%{http_code}" --max-time 60 \
      -u "${WPUT_USER}:${WPUT_PASSWORD}" -T "${local_file}" "${dest_url}" || true
  )"
  echo "log_offload: PUT $(basename "${local_file}") HTTP ${code}"
  case "${code}" in
    200|201|204) return 0 ;;
    *) return 1 ;;
  esac
}

upload_ok=0
if [[ "${MODE}" == "production" ]] && [[ -n "${HTTPS_DEST}" ]] && command -v curl >/dev/null 2>&1; then
  if [[ -z "${WPUT_USER:-}" || -z "${WPUT_PASSWORD:-}" ]]; then
    echo "log_offload: WPUT_USER/WPUT_PASSWORD missing (wput.env) — skip IIS PUT (archives kept)"
  else
    uploaded=0
    failed=0
    ensure_remote_folder "${HTTPS_DEST}"
    shopt -s nullglob
    for f in "${ARCHIVE_DIR}/${NAME_PREFIX}-"*; do
      [[ -f "${f}" ]] || continue
      case "${f}" in
        *.gz|*.txt|*.log|*.jsonl) ;;
        *) continue ;;
      esac
      if [[ -n "${ASSET}" ]] && [[ "$(basename "${f}")" != "${ASSET}-"* ]]; then
        echo "log_offload: skip non-asset archive $(basename "${f}")"
        continue
      fi
      dest_url="${HTTPS_DEST}$(basename "${f}")"
      echo "log_offload: curl -T $(basename "${f}") -> ${HTTPS_DEST}"
      if put_one "${f}" "${dest_url}"; then
        if confirm_put "${dest_url}" "${f}"; then
          rm -f "${f}"
          echo "deleted after confirm: ${f}"
          uploaded=$((uploaded + 1))
        else
          echo "WARN: PUT confirm failed — keeping ${f}"
          failed=$((failed + 1))
        fi
      else
        echo "WARN: PUT failed — keeping ${f}"
        failed=$((failed + 1))
      fi
    done
    shopt -u nullglob
    if [[ "${uploaded}" -gt 0 ]] && [[ "${failed}" -eq 0 ]]; then
      upload_ok=1
    elif [[ "${uploaded}" -eq 0 ]] && [[ "${failed}" -eq 0 ]]; then
      echo "log_offload: no asset-named archives to upload"
    fi
  fi
elif [[ "${MODE}" == "production" ]]; then
  echo "log_offload: curl missing or no HTTPS dest — skip IIS PUT (archives kept)"
else
  echo "log_offload: skip IIS PUT"
fi

cap_archives() {
  local total=0
  local f
  mapfile -t files < <(find "${ARCHIVE_DIR}" -type f -printf '%T@ %s %p\n' 2>/dev/null | sort -n)
  for line in "${files[@]+"${files[@]}"}"; do
    [[ -n "${line}" ]] || continue
    total=$((total + $(echo "${line}" | awk '{print $2}')))
  done
  if [[ "${total}" -le "${ARCHIVE_CAP_BYTES}" ]]; then
    return 0
  fi
  echo "log_offload: archive ${total} bytes exceeds cap ${ARCHIVE_CAP_BYTES} — drop oldest"
  for line in "${files[@]+"${files[@]}"}"; do
    [[ -n "${line}" ]] || continue
    if [[ "${total}" -le "${ARCHIVE_CAP_BYTES}" ]]; then
      break
    fi
    f="$(echo "${line}" | awk '{print $3}')"
    sz="$(echo "${line}" | awk '{print $2}')"
    rm -f "${f}"
    total=$((total - sz))
    echo "dropped oldest archive ${f}"
  done
}
cap_archives

echo "log_offload ok upload_ok=${upload_ok} mode=${MODE} asset=${ASSET:-none} dest=${HTTPS_DEST:-none} kind=${LOG_DEST_KIND:-none}"
