#!/usr/bin/env bash
# =============================================================================
# onx-customer-slice-create — Create / update per-customer systemd slice
#
# v86.1 — Multi-tenant isolation: per-customer slice generator.
#
# Renders /usr/local/onoxsoft/templates/customer-template.slice.stub →
# /etc/systemd/system/customer-${USERNAME}.slice, then runs daemon-reload
# and starts the slice. Idempotent: re-render OK, existing slice is updated
# atomically (write to tmp + install -m 0644 + reload).
#
# Input (stdin JSON):
#   {
#     "username":       "onx_leafport",     -- required, ^onx_[a-z0-9]{4,30}$
#     "memory_mb":      2048,               -- hard MemoryMax (MiB), default 1024
#     "cpu_percent":    200,                -- CPUQuota % (100 = 1 core), default 100
#     "tasks_max":      500,                -- TasksMax, default 500
#     "io_read_mbps":   50,                 -- IOReadBandwidthMax (MiB/s), default 50
#     "io_write_mbps":  30                  -- IOWriteBandwidthMax (MiB/s), default 30
#   }
#
# Output (stdout JSON):
#   {
#     "ok": true,
#     "username": "onx_leafport",
#     "slice_unit": "customer-onx_leafport.slice",
#     "slice_path": "/etc/systemd/system/customer-onx_leafport.slice",
#     "runtime_path": "/sys/fs/cgroup/onoxsoft.slice/customer-onx_leafport.slice",
#     "parent": "onoxsoft.slice",
#     "memory_mb": 2048,
#     "cpu_percent": 200,
#     "tasks_max": 500,
#     "io_read_mbps": 50,
#     "io_write_mbps": 30,
#     "active": true,
#     "created": true        -- false when re-rendering existing slice
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail
#
# Deployed to: /usr/local/onoxsoft/bin/onx-customer-slice-create
# =============================================================================

set -euo pipefail

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

# ── Constants ────────────────────────────────────────────────────────────────
TEMPLATE_PATH="${ONX_SLICE_TEMPLATE_PATH:-/usr/local/onoxsoft/templates/customer-template.slice.stub}"
SLICE_DIR="/etc/systemd/system"
PARENT_SLICE="onoxsoft.slice"

# ── Dependencies ─────────────────────────────────────────────────────────────
command -v jq        >/dev/null 2>&1 || onx_die 2 "jq required"
command -v systemctl >/dev/null 2>&1 || onx_die 2 "systemctl required (systemd system)"
command -v install   >/dev/null 2>&1 || onx_die 2 "install (coreutils) required"
require_root

# ── Read & parse stdin ───────────────────────────────────────────────────────
INPUT="$(cat)"
onx_require_json "${INPUT}"

USERNAME=$(onx_json_get  "${INPUT}" "username")
MEM_MB=$(onx_json_get    "${INPUT}" "memory_mb"      "1024")
CPU_PCT=$(onx_json_get   "${INPUT}" "cpu_percent"    "100")
TASKS_MAX=$(onx_json_get "${INPUT}" "tasks_max"      "500")
IO_READ=$(onx_json_get   "${INPUT}" "io_read_mbps"   "50")
IO_WRITE=$(onx_json_get  "${INPUT}" "io_write_mbps"  "30")

# ── Input validation ─────────────────────────────────────────────────────────
# Note: cgroup paths allow up to ~30 chars for username — we widen the regex
# beyond the standard onx_[4-12] to be future-proof.
[[ "${USERNAME}" =~ ^onx_[a-z0-9]{4,30}$ ]] \
    || onx_die 1 "invalid username '${USERNAME}': must match ^onx_[a-z0-9]{4,30}\$"

[[ "${MEM_MB}"    =~ ^[0-9]+$ ]] || onx_die 1 "memory_mb must be a positive integer"
[[ "${CPU_PCT}"   =~ ^[0-9]+$ ]] || onx_die 1 "cpu_percent must be a positive integer"
[[ "${TASKS_MAX}" =~ ^[0-9]+$ ]] || onx_die 1 "tasks_max must be a positive integer"
[[ "${IO_READ}"   =~ ^[0-9]+$ ]] || onx_die 1 "io_read_mbps must be a positive integer"
[[ "${IO_WRITE}"  =~ ^[0-9]+$ ]] || onx_die 1 "io_write_mbps must be a positive integer"

(( MEM_MB    >= 128 && MEM_MB    <= 524288 )) || onx_die 1 "memory_mb out of range [128-524288]"
(( CPU_PCT   >= 10  && CPU_PCT   <= 10000  )) || onx_die 1 "cpu_percent out of range [10-10000]"
(( TASKS_MAX >= 20  && TASKS_MAX <= 100000 )) || onx_die 1 "tasks_max out of range [20-100000]"
(( IO_READ   >= 1   && IO_READ   <= 10000  )) || onx_die 1 "io_read_mbps out of range [1-10000]"
(( IO_WRITE  >= 1   && IO_WRITE  <= 10000  )) || onx_die 1 "io_write_mbps out of range [1-10000]"

# ── Preflight ────────────────────────────────────────────────────────────────
[[ -r "${TEMPLATE_PATH}" ]] || onx_die 2 "slice template not found or unreadable: ${TEMPLATE_PATH}"
[[ -d "${SLICE_DIR}"    ]] || onx_die 2 "systemd system unit dir missing: ${SLICE_DIR}"

# Confirm parent slice is installed (we depend on onoxsoft.slice Delegate)
if ! systemctl cat "${PARENT_SLICE}" >/dev/null 2>&1; then
    onx_die 2 "parent slice '${PARENT_SLICE}' is not installed — run onx-onoxsoft-slice-bootstrap first"
fi

# ── Derived values ───────────────────────────────────────────────────────────
SLICE_UNIT="customer-${USERNAME}.slice"
SLICE_PATH="${SLICE_DIR}/${SLICE_UNIT}"
RUNTIME_PATH="/sys/fs/cgroup/${PARENT_SLICE}/${SLICE_UNIT}"

# MemoryHigh = 90% of MemoryMax (soft cap before OOM)
MEM_HIGH_MB=$(( MEM_MB * 90 / 100 ))
(( MEM_HIGH_MB < 64 )) && MEM_HIGH_MB=64

# IO device — prefer /var/www, fallback /home/users, else /home, else "/" root.
# systemd accepts a path here and resolves it to the backing block device.
IO_DEVICE="/var/www"
if [[ ! -d "${IO_DEVICE}" ]]; then
    for cand in /home/users /home /; do
        if [[ -d "${cand}" ]]; then
            IO_DEVICE="${cand}"
            break
        fi
    done
fi

# Track whether this is a fresh create vs re-render (idempotent update)
CREATED="true"
[[ -f "${SLICE_PATH}" ]] && CREATED="false"

# ── Render template ──────────────────────────────────────────────────────────
TMP_SLICE="$(mktemp /tmp/onx-slice-XXXXXX.slice)"
trap 'rm -f "${TMP_SLICE}"' EXIT

cp "${TEMPLATE_PATH}" "${TMP_SLICE}"

# Safe sed-replace helper (escapes &, \, /). Same pattern as onx-fpm-pool-create.
sed_replace() {
    local var="$1" val="$2"
    local escaped
    escaped=$(printf '%s' "${val}" | sed 's/[&\\/]/\\&/g')
    sed -i "s|\${${var}}|${escaped}|g" "${TMP_SLICE}"
}

sed_replace "USERNAME"        "${USERNAME}"
sed_replace "MEMORY_MAX_MB"   "${MEM_MB}"
sed_replace "MEMORY_HIGH_MB"  "${MEM_HIGH_MB}"
sed_replace "CPU_QUOTA"       "${CPU_PCT}"
sed_replace "TASKS_MAX"       "${TASKS_MAX}"
sed_replace "IO_READ_DEVICE"  "${IO_DEVICE}"
sed_replace "IO_READ_MBPS"    "${IO_READ}"
sed_replace "IO_WRITE_MBPS"   "${IO_WRITE}"

# Sanity: no unresolved placeholders should remain
if grep -q '\${[A-Z_]\+}' "${TMP_SLICE}"; then
    UNRESOLVED="$(grep -oE '\$\{[A-Z_]+\}' "${TMP_SLICE}" | sort -u | tr '\n' ' ')"
    onx_die 3 "unresolved placeholders in rendered slice: ${UNRESOLVED}"
fi

# Atomic install (mode 0644 — slice units are world-readable)
install -m 0644 -o root -g root "${TMP_SLICE}" "${SLICE_PATH}"

# ── Reload systemd & start slice ─────────────────────────────────────────────
systemctl daemon-reload || onx_die 3 "systemctl daemon-reload failed"

# Slices cannot be 'stopped' in the conventional sense, but `start` is the
# canonical way to materialise the cgroup hierarchy + apply properties.
if ! systemctl start "${SLICE_UNIT}" >/dev/null 2>&1; then
    onx_die 3 "systemctl start ${SLICE_UNIT} failed"
fi

# ── Verify ───────────────────────────────────────────────────────────────────
ACTIVE="false"
if systemctl is-active --quiet "${SLICE_UNIT}" 2>/dev/null; then
    ACTIVE="true"
fi

onx_log "slice-create: ${USERNAME} mem=${MEM_MB}M cpu=${CPU_PCT}% tasks=${TASKS_MAX} io=R${IO_READ}/W${IO_WRITE} active=${ACTIVE} created=${CREATED}"

# ── Output ───────────────────────────────────────────────────────────────────
jq -nc \
    --arg username      "${USERNAME}" \
    --arg slice_unit    "${SLICE_UNIT}" \
    --arg slice_path    "${SLICE_PATH}" \
    --arg runtime_path  "${RUNTIME_PATH}" \
    --arg parent        "${PARENT_SLICE}" \
    --argjson memory_mb     "${MEM_MB}" \
    --argjson cpu_percent   "${CPU_PCT}" \
    --argjson tasks_max     "${TASKS_MAX}" \
    --argjson io_read_mbps  "${IO_READ}" \
    --argjson io_write_mbps "${IO_WRITE}" \
    --argjson active        "${ACTIVE}" \
    --argjson created       "${CREATED}" \
    '{
        ok:            true,
        username:      $username,
        slice_unit:    $slice_unit,
        slice_path:    $slice_path,
        runtime_path:  $runtime_path,
        parent:        $parent,
        memory_mb:     $memory_mb,
        cpu_percent:   $cpu_percent,
        tasks_max:     $tasks_max,
        io_read_mbps:  $io_read_mbps,
        io_write_mbps: $io_write_mbps,
        active:        $active,
        created:       $created
    }'
