#!/usr/bin/env bash
# =============================================================================
# onx-du-account — Per-account disk usage breakdown + top files / directories
#
# Input (stdin JSON):
#   {
#     "username":  "onx_xxxx",
#     "home":      "/home/onx_xxxx",       # optional, defaults to /home/<username>
#     "top_count": 20                       # optional, defaults to 20
#   }
#
# Output (stdout JSON):
#   {
#     "username": "...",
#     "home":     "/home/onx_xxxx",
#     "total_bytes": 1342177280,
#     "breakdown": {
#       "public_html": 845_000_000,
#       "Maildir":     180_000_000,
#       "databases":   ...,            # mysql data dir per-db (when readable)
#       "logs":        ...,
#       "backups":     ...,
#       "tmp":         ...,
#       "private":     ...
#     },
#     "top_files": [
#       {"path": "public_html/wp-content/uploads/video.mp4", "size": 245000000},
#       ...
#     ],
#     "top_directories": [
#       {"path": "public_html/wp-content/uploads", "size": 720000000, "file_count": 4521},
#       ...
#     ]
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=execution-fail
# Deployed to: /usr/local/onoxsoft/bin/onx-du-account
# =============================================================================

set -euo pipefail

SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
# shellcheck source=_lib/common.sh
source "${SCRIPT_DIR}/_lib/common.sh"

require_cmd du
require_cmd find
require_cmd jq
require_cmd awk
require_cmd sort

onx_json_input

USERNAME=$(onx_json_field "username")
HOME_DIR=$(onx_json_field "home")
TOP_COUNT=$(onx_json_field "top_count" "20")

onx_validate_username "${USERNAME}"

# Default home dir
if [[ -z "${HOME_DIR}" ]]; then
    HOME_DIR="/home/${USERNAME}"
fi

# Hardening: home must live under /home and the basename must equal username
case "${HOME_DIR}" in
    /home/*) ;;
    *) onx_die 1 "home must live under /home (got '${HOME_DIR}')" ;;
esac
if [[ "$(basename "${HOME_DIR}")" != "${USERNAME}" ]]; then
    onx_die 1 "home basename mismatch"
fi
if [[ ! -d "${HOME_DIR}" ]]; then
    # Missing home — degrade gracefully, return zeros
    jq -nc \
        --arg username "${USERNAME}" \
        --arg home "${HOME_DIR}" \
        '{
            username: $username,
            home: $home,
            total_bytes: 0,
            breakdown: {},
            top_files: [],
            top_directories: [],
            note: "home directory does not exist"
        }'
    exit 0
fi

# Top count: integer, 1..200
if ! [[ "${TOP_COUNT}" =~ ^[0-9]+$ ]]; then
    TOP_COUNT=20
fi
if (( TOP_COUNT > 200 )); then
    TOP_COUNT=200
elif (( TOP_COUNT < 1 )); then
    TOP_COUNT=20
fi

# ── Helpers ─────────────────────────────────────────────────────────────────
_du_bytes() {
    local p="$1"
    if [[ -d "$p" ]]; then
        du -sb "$p" 2>/dev/null | awk '{print $1; exit}'
    else
        printf '0'
    fi
}

_count_files() {
    local p="$1"
    if [[ -d "$p" ]]; then
        find "$p" -type f 2>/dev/null | wc -l
    else
        printf '0'
    fi
}

# ── Total ───────────────────────────────────────────────────────────────────
TOTAL_BYTES=$(_du_bytes "${HOME_DIR}")
TOTAL_BYTES="${TOTAL_BYTES:-0}"

# ── Breakdown (well-known cPanel-style subdirs) ──────────────────────────────
PUBLIC_HTML_BYTES=$(_du_bytes "${HOME_DIR}/public_html")
MAILDIR_BYTES=$(_du_bytes "${HOME_DIR}/Maildir")
MAIL_BYTES=$(_du_bytes "${HOME_DIR}/mail")          # cPanel-style nested per-domain mail
LOGS_BYTES=$(_du_bytes "${HOME_DIR}/logs")
BACKUPS_BYTES=$(_du_bytes "${HOME_DIR}/backups")
TMP_BYTES=$(_du_bytes "${HOME_DIR}/tmp")
PRIVATE_BYTES=$(_du_bytes "${HOME_DIR}/private")

# MySQL databases per account: read from /var/lib/mysql/<username>_* if readable
DB_BYTES=0
if [[ -r /var/lib/mysql ]]; then
    # nullglob: empty pattern → no error
    shopt -s nullglob
    _db_dirs=( /var/lib/mysql/${USERNAME}_* )
    shopt -u nullglob
    if [[ ${#_db_dirs[@]} -gt 0 ]]; then
        DB_BYTES=$(du -sb "${_db_dirs[@]}" 2>/dev/null | awk '{sum+=$1} END {print sum+0}')
    fi
fi
DB_BYTES="${DB_BYTES:-0}"

# Combine maildir + cpanel-style mail
COMBINED_MAIL=$(( MAILDIR_BYTES + MAIL_BYTES ))

# ── Top files (under home, max depth 8 to avoid runaway traversal) ──────────
TOP_FILES_JSON="[]"
if command -v find >/dev/null 2>&1; then
    TOP_FILES_JSON=$(find "${HOME_DIR}" -xdev -maxdepth 8 -type f -printf '%s %P\n' 2>/dev/null \
        | sort -rn \
        | head -n "${TOP_COUNT}" \
        | awk '{
            size=$1;
            $1="";
            sub(/^ /, "");
            printf "{\"size\":%s,\"path\":\"%s\"}\n", size, $0
          }' \
        | jq -s '.' 2>/dev/null || echo '[]')
    TOP_FILES_JSON="${TOP_FILES_JSON:-[]}"
fi

# ── Top directories (immediate + first-level subdirs) ────────────────────────
TOP_DIRS_JSON="[]"
TOP_DIRS_TMP=$(mktemp -t onx-du-dirs.XXXXXX)
trap '[[ -f "${TOP_DIRS_TMP}" ]] && rm -f "${TOP_DIRS_TMP}"' EXIT

shopt -s nullglob
_dir_targets=( "${HOME_DIR}"/* "${HOME_DIR}"/*/* )
shopt -u nullglob

if [[ ${#_dir_targets[@]} -gt 0 ]]; then
    du -sb "${_dir_targets[@]}" 2>/dev/null \
        | sort -rn \
        | head -n "${TOP_COUNT}" \
        > "${TOP_DIRS_TMP}" || true
fi

if [[ -s "${TOP_DIRS_TMP}" ]]; then
    DIRS_LINES=()
    # du -sb output is "<size>\t<path>" but bash's read with default IFS
    # collapses tabs+spaces. Force IFS to a single tab for safety.
    while IFS=$'\t ' read -r SIZE PATH_RAW; do
        [[ -z "${SIZE}" ]] && continue
        # Strip HOME_DIR prefix so output is relative
        REL_PATH="${PATH_RAW#${HOME_DIR}/}"
        REL_PATH="${REL_PATH#${HOME_DIR}}"
        [[ -z "${REL_PATH}" ]] && continue
        FCOUNT=$(find "${PATH_RAW}" -type f 2>/dev/null | wc -l)
        DIRS_LINES+=("$(jq -nc \
            --arg path "${REL_PATH}" \
            --argjson size "${SIZE}" \
            --argjson file_count "${FCOUNT}" \
            '{path:$path,size:$size,file_count:$file_count}')")
    done < "${TOP_DIRS_TMP}"

    if [[ ${#DIRS_LINES[@]} -gt 0 ]]; then
        TOP_DIRS_JSON=$(printf '%s\n' "${DIRS_LINES[@]}" | jq -s '.')
    fi
fi

# ── Compose final JSON ──────────────────────────────────────────────────────
jq -nc \
    --arg username "${USERNAME}" \
    --arg home "${HOME_DIR}" \
    --argjson total       "${TOTAL_BYTES}" \
    --argjson public_html "${PUBLIC_HTML_BYTES}" \
    --argjson maildir     "${COMBINED_MAIL}" \
    --argjson databases   "${DB_BYTES}" \
    --argjson logs        "${LOGS_BYTES}" \
    --argjson backups     "${BACKUPS_BYTES}" \
    --argjson tmp         "${TMP_BYTES}" \
    --argjson private     "${PRIVATE_BYTES}" \
    --argjson top_files   "${TOP_FILES_JSON}" \
    --argjson top_dirs    "${TOP_DIRS_JSON}" \
    '{
       username: $username,
       home: $home,
       total_bytes: $total,
       breakdown: {
         public_html: $public_html,
         Maildir:     $maildir,
         databases:   $databases,
         logs:        $logs,
         backups:     $backups,
         tmp:         $tmp,
         private:     $private
       },
       top_files: $top_files,
       top_directories: $top_dirs
     }'
