#!/usr/bin/env bash
# =============================================================================
# onx-default-page-deploy — Deploy branded placeholder index.html (v87)
#
# Purpose:
#   Renders a state-specific HTML template
#   (provisioning|new|suspended|terminated) into a customer's docroot as
#   `index.html`, performing envsubst-style placeholder substitution from
#   the input JSON. Wraps existing index.html in `.user-bak` when overwriting
#   for suspended/terminated states. Idempotent — re-deploying the same state
#   is safe; deploying over a customer's own index.html for new/provisioning
#   states is a no-op (marker comment is the discriminator).
#
# v87 — Account-state branded placeholders ekleme. user-add / suspend /
# unsuspend / terminate sysapi'lerinden tetiklenir.
#
# Input (stdin JSON):
#   {
#     "username":      "onx_xxxx",                   -- required
#     "domain":        "example.com",                -- required (display only)
#     "state":         "provisioning|new|suspended|terminated",   -- required
#     "docroot":       "/home/users/onx_xxxx/public_html",        -- required
#     "lang":          "tr",                         -- optional, default tr
#     "brand_name":    "Onoxsoft Hosting",           -- optional
#     "brand_logo_url": "data:image/svg...",         -- optional
#     "brand_color":   "#6366f1",                    -- optional (primary)
#     "brand_color_accent": "#8b5cf6",               -- optional
#     "support_email": "support@onoxsoft.com.tr",    -- optional
#     "support_url":   "https://onoxsoft.com.tr/destek", -- optional
#     "created_at":    "26 Mayıs 2026",              -- optional (display)
#     "suspended_at":  "26 Mayıs 2026",              -- optional (state=suspended)
#     "terminated_at": "26 Mayıs 2026",              -- optional (state=terminated)
#     "ftp_host":      "panel.onoxsoft.com.tr",      -- optional (state=new)
#     "ftp_port":      "21",                         -- optional (state=new)
#     "ssh_port":      "22",                         -- optional (state=new)
#     "force":         false                         -- optional; overwrite even if user content present
#   }
#
# Output (stdout JSON):
#   {
#     "ok": true,
#     "state": "provisioning",
#     "deployed_to": "/home/.../public_html/index.html",
#     "size_bytes": 9847,
#     "backed_up_original": "/home/.../public_html/index.html.user-bak" | null,
#     "skipped": false,
#     "skip_reason": null
#   }
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail
#
# Sudoers entry:
#   apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-default-page-deploy
#
# Deployed to: /usr/local/onoxsoft/bin/onx-default-page-deploy
# =============================================================================

set -euo pipefail

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

# ── Constants ─────────────────────────────────────────────────────────────────
# Production layout. Override with ONX_DEFAULT_PAGES_DIR env var (tests/dev).
TEMPLATES_DIR="${ONX_DEFAULT_PAGES_DIR:-/usr/local/onoxsoft/templates/default-pages}"
PLACEHOLDER_MARKER="ONOX-PLACEHOLDER"
DEFAULT_GROUP="onoxsoft-users"
ALT_GROUPS=("apache" "nginx" "webserver" "nobody")
VALID_STATES=("provisioning" "new" "suspended" "terminated")

# ── Dependencies ──────────────────────────────────────────────────────────────
command -v jq       >/dev/null 2>&1 || onx_die 2 "jq required"
command -v envsubst >/dev/null 2>&1 || onx_die 2 "envsubst (gettext) required"
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")
DOMAIN=$(onx_json_get   "${INPUT}" "domain"  "")
STATE=$(onx_json_get    "${INPUT}" "state"   "")
DOCROOT=$(onx_json_get  "${INPUT}" "docroot" "")
LANG_CODE=$(onx_json_get "${INPUT}" "lang"   "tr")
FORCE=$(onx_json_get_bool "${INPUT}" "force"  "false")

BRAND_NAME=$(onx_json_get          "${INPUT}" "brand_name"          "Onoxsoft Hosting")
BRAND_LOGO_URL=$(onx_json_get      "${INPUT}" "brand_logo_url"      "")
BRAND_COLOR_PRIMARY=$(onx_json_get "${INPUT}" "brand_color"         "#6366f1")
BRAND_COLOR_ACCENT=$(onx_json_get  "${INPUT}" "brand_color_accent"  "#8b5cf6")
SUPPORT_EMAIL=$(onx_json_get       "${INPUT}" "support_email"       "support@onoxsoft.com.tr")
SUPPORT_URL=$(onx_json_get         "${INPUT}" "support_url"         "https://onoxsoft.com.tr/destek")
CREATED_AT=$(onx_json_get          "${INPUT}" "created_at"          "")
SUSPENDED_AT=$(onx_json_get        "${INPUT}" "suspended_at"        "")
TERMINATED_AT=$(onx_json_get       "${INPUT}" "terminated_at"       "")
FTP_HOST=$(onx_json_get            "${INPUT}" "ftp_host"            "")
FTP_PORT=$(onx_json_get            "${INPUT}" "ftp_port"            "21")
SSH_PORT=$(onx_json_get            "${INPUT}" "ssh_port"            "22")

# ── Input validation ──────────────────────────────────────────────────────────
onx_validate_username "${USERNAME}"

# State whitelist
STATE_OK=0
for s in "${VALID_STATES[@]}"; do
    [[ "${STATE}" == "$s" ]] && STATE_OK=1 && break
done
[[ "$STATE_OK" -eq 1 ]] || onx_die 1 "invalid state '${STATE}': must be one of ${VALID_STATES[*]}"

[[ -n "${DOCROOT}" ]] || onx_die 1 "docroot is required"

# Reject obvious traversal / absolute-root attacks. Production docroot is
# /home/<user>/public_html VEYA /home/users/<user>/public_html VEYA per-domain
# /home/users/<user>/<domain>/public_html — guard against `/`, `/etc`, `..`.
# v3.67 FIX: Eski guard yalnız /home/<user>/public_html (2 seviye) kabul ediyordu;
# bu sunucu /home/users/<user>/public_html (3 seviye) kullandığından deploy HER
# zaman "docroot must be under /home/" ile exit 1 oluyordu → branded suspended/
# terminated/provisioning sayfası HİÇ yazılmıyordu (sunucu geneli). Derin
# public_html pattern'leri eklendi.
case "${DOCROOT}" in
    /home/*/public_html|/home/*/public_html/ \
    |/home/*/*/public_html|/home/*/*/public_html/ \
    |/home/*/*/*/public_html|/home/*/*/*/public_html/ \
    |/home/*/*) : ;;
    *) onx_die 1 "docroot must be under /home/: '${DOCROOT}'" ;;
esac
[[ "${DOCROOT}" == *".."* ]] && onx_die 1 "docroot contains '..' — refused"

# Lang sanity (tr|en only — keeps template surface tight)
[[ "${LANG_CODE}" =~ ^(tr|en)$ ]] || LANG_CODE="tr"

# Domain optional but if provided must look domain-ish (skip strict validation
# — internal callers pass placeholders like 'unknown' during partial provision)
if [[ -n "${DOMAIN}" && "${DOMAIN}" != "unknown" ]]; then
    # Don't onx_die — soft-allow odd values; just sanitise XSS-style characters.
    DOMAIN="${DOMAIN//[<>\"\']/}"
fi

# Strip CR/LF + HTML special chars from anything that lands in HTML attrs.
sanitise() {
    local v="$1"
    v="${v//[$'\n\r']/}"
    printf '%s' "${v//[<>]/}"
}
DOMAIN=$(sanitise "${DOMAIN}")
BRAND_NAME=$(sanitise "${BRAND_NAME}")
SUPPORT_EMAIL=$(sanitise "${SUPPORT_EMAIL}")
SUPPORT_URL=$(sanitise "${SUPPORT_URL}")
CREATED_AT=$(sanitise "${CREATED_AT}")
SUSPENDED_AT=$(sanitise "${SUSPENDED_AT}")
TERMINATED_AT=$(sanitise "${TERMINATED_AT}")
FTP_HOST=$(sanitise "${FTP_HOST}")

# Date fallbacks
if [[ -z "${CREATED_AT}" ]]; then
    CREATED_AT=$(date +"%d %B %Y" 2>/dev/null || date +"%Y-%m-%d")
fi
[[ -z "${SUSPENDED_AT}"  ]] && SUSPENDED_AT="$(date +"%d %B %Y" 2>/dev/null || date +"%Y-%m-%d")"
[[ -z "${TERMINATED_AT}" ]] && TERMINATED_AT="$(date +"%d %B %Y" 2>/dev/null || date +"%Y-%m-%d")"

# ── Preflight ─────────────────────────────────────────────────────────────────
TEMPLATE_PATH="${TEMPLATES_DIR}/${STATE}.html.stub"

[[ -d "${TEMPLATES_DIR}" ]] || onx_die 2 "templates dir not found: ${TEMPLATES_DIR}"
[[ -r "${TEMPLATE_PATH}" ]] || onx_die 2 "template not found or unreadable: ${TEMPLATE_PATH}"

# v3.50 FIX: Docroot YOKSA AUTO-CREATE et (eskiden skip ediyordu).
# Skip ettiğinde unsuspend → 404 sorunu çıkıyordu (suspended.html silindi,
# yeni docroot yok). Şimdi parent dizini var ve Linux user mevcutsa, public_html'i
# yarat + permission ayarla.
if [[ ! -d "${DOCROOT}" ]]; then
    PARENT_DIR=$(dirname "${DOCROOT}")
    if [[ -d "${PARENT_DIR}" ]] && id -u "${USERNAME}" >/dev/null 2>&1; then
        onx_log "default-page-deploy: docroot missing, AUTO-CREATE user=${USERNAME} docroot=${DOCROOT}"
        mkdir -p "${DOCROOT}"
        # Linux user'ın primary group'unu kullan (genelde onoxsoft-users)
        USER_GRP=$(id -gn "${USERNAME}" 2>/dev/null || echo "onoxsoft-users")
        chown "${USERNAME}:${USER_GRP}" "${DOCROOT}" 2>/dev/null || true
        chgrp webserver "${DOCROOT}" 2>/dev/null || true
        chmod 0750 "${DOCROOT}" 2>/dev/null || true
        command -v restorecon >/dev/null && restorecon "${DOCROOT}" 2>/dev/null || true
    else
        # Linux user yok veya parent yok → gerçekten provisioning bekleniyor
        onx_log "default-page-deploy: docroot missing + no user/parent, skip user=${USERNAME} state=${STATE} docroot=${DOCROOT}"
        onx_json_out \
            "ok"          "true" \
            "state"       "${STATE}" \
            "deployed_to" "" \
            "size_bytes"  "0" \
            "backed_up_original" "null" \
            "skipped"     "true" \
            "skip_reason" "docroot_missing_no_user_or_parent"
        exit 0
    fi
fi

INDEX_PATH="${DOCROOT}/index.html"
BACKUP_PATH=""

# ── Idempotency / overwrite policy ────────────────────────────────────────────
# Determine whether existing index.html is one of:
#   (a) absent — write freely
#   (b) ours (marker comment present) — overwrite freely
#   (c) user content — depends on state:
#         provisioning / new: SKIP (we never overwrite user content silently)
#         suspended  / terminated: BACKUP then overwrite (admin action)
#   force=true overrides — admin can force-deploy any state.

IS_OURS=0
HAS_INDEX=0
if [[ -f "${INDEX_PATH}" ]]; then
    HAS_INDEX=1
    if grep -q -F "${PLACEHOLDER_MARKER}" "${INDEX_PATH}" 2>/dev/null; then
        IS_OURS=1
    fi
fi

case "${STATE}" in
    provisioning|new)
        if [[ "${HAS_INDEX}" -eq 1 && "${IS_OURS}" -eq 0 && "${FORCE}" != "true" ]]; then
            onx_log "default-page-deploy: user content present, skip user=${USERNAME} state=${STATE}"
            onx_json_out \
                "ok"           "true" \
                "state"        "${STATE}" \
                "deployed_to"  "${INDEX_PATH}" \
                "size_bytes"   "0" \
                "backed_up_original" "null" \
                "skipped"      "true" \
                "skip_reason"  "user_content_present"
            exit 0
        fi
        ;;
    suspended|terminated)
        # Admin-driven; ALWAYS replace, but if there's user content, preserve it.
        if [[ "${HAS_INDEX}" -eq 1 && "${IS_OURS}" -eq 0 ]]; then
            BACKUP_PATH="${INDEX_PATH}.user-bak"
            # Don't clobber an existing backup (multiple suspends → keep first)
            if [[ ! -f "${BACKUP_PATH}" ]]; then
                if cp -p "${INDEX_PATH}" "${BACKUP_PATH}" 2>/dev/null; then
                    onx_log "default-page-deploy: backed up user content → ${BACKUP_PATH}"
                else
                    onx_log "default-page-deploy: WARN backup cp failed (continuing): ${INDEX_PATH}"
                    BACKUP_PATH=""
                fi
            else
                onx_log "default-page-deploy: pre-existing .user-bak preserved: ${BACKUP_PATH}"
            fi
        fi
        ;;
esac

# ── Render template via envsubst ──────────────────────────────────────────────
TMP_FILE="$(mktemp -t onx-page-XXXXXX.html)"
trap 'rm -f "${TMP_FILE}"' EXIT

TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

# Build whitelist of placeholders envsubst should replace (prevents accidental
# substitution of user-supplied tokens that look like ${SOMETHING} inside the
# template — envsubst with no whitelist substitutes EVERY $VAR in env). The
# whitelist is a single string of space-separated variable names.
ENVSUBST_VARS='${LANG} ${DOMAIN} ${USERNAME} ${BRAND_NAME} ${BRAND_LOGO_URL} ${BRAND_COLOR_PRIMARY} ${BRAND_COLOR_ACCENT} ${SUPPORT_EMAIL} ${SUPPORT_URL} ${CREATED_AT} ${SUSPENDED_AT} ${TERMINATED_AT} ${FTP_HOST} ${FTP_PORT} ${SSH_PORT}'

# Export only the substitution variables — envsubst reads from environment.
export LANG="${LANG_CODE}"
export DOMAIN USERNAME BRAND_NAME BRAND_LOGO_URL BRAND_COLOR_PRIMARY BRAND_COLOR_ACCENT
export SUPPORT_EMAIL SUPPORT_URL CREATED_AT SUSPENDED_AT TERMINATED_AT
export FTP_HOST FTP_PORT SSH_PORT

if ! envsubst "${ENVSUBST_VARS}" < "${TEMPLATE_PATH}" > "${TMP_FILE}" 2>/dev/null; then
    onx_die 3 "envsubst rendering failed for ${TEMPLATE_PATH}"
fi

# Inject deploy timestamp into the marker comment (templates already carry
# `ONOX-PLACEHOLDER state=X template-version=N`; we APPEND `deployed=ISO`
# only on the first marker line so future restore can tell deploy age).
# Use sed in-place on first matching <!-- ONOX-PLACEHOLDER ... --> line.
if grep -q -F "${PLACEHOLDER_MARKER}" "${TMP_FILE}"; then
    sed -i "0,/${PLACEHOLDER_MARKER} /{s|${PLACEHOLDER_MARKER} |${PLACEHOLDER_MARKER} deployed=${TIMESTAMP} |}" "${TMP_FILE}" || true
fi

# Sanity — must still have <html> tag and our marker after rendering
grep -q "<html" "${TMP_FILE}" || onx_die 3 "rendered template missing <html> tag"
grep -q -F "${PLACEHOLDER_MARKER}" "${TMP_FILE}" || onx_die 3 "rendered template missing marker"

# ── Determine target group (Apache/Nginx/webserver — fallback DEFAULT_GROUP) ─
TARGET_GROUP=""
for g in "${DEFAULT_GROUP}" "${ALT_GROUPS[@]}"; do
    if getent group "${g}" >/dev/null 2>&1; then
        TARGET_GROUP="${g}"
        break
    fi
done
[[ -z "${TARGET_GROUP}" ]] && TARGET_GROUP="${USERNAME}"

# Ensure target user exists (skip otherwise)
if ! id "${USERNAME}" >/dev/null 2>&1; then
    onx_log "default-page-deploy: user '${USERNAME}' missing — using root:${TARGET_GROUP}"
    OWNER_USER="root"
else
    OWNER_USER="${USERNAME}"
fi

# ── Atomic install ────────────────────────────────────────────────────────────
# install(1): atomic rename, owner+group+mode in one shot. Falls back to
# cp+chmod+chown if `install` lacks -o/-g on this platform.
if ! install -m 0644 -o "${OWNER_USER}" -g "${TARGET_GROUP}" "${TMP_FILE}" "${INDEX_PATH}" 2>/dev/null; then
    # Fallback path — `install` -o/-g may fail in containers / RHEL-busybox
    cp "${TMP_FILE}" "${INDEX_PATH}" || onx_die 3 "cp to ${INDEX_PATH} failed"
    chmod 0644 "${INDEX_PATH}" || true
    chown "${OWNER_USER}:${TARGET_GROUP}" "${INDEX_PATH}" 2>/dev/null || true
fi

# SELinux context — Apache/Nginx requires httpd_sys_content_t to serve.
# Best-effort; non-RHEL hosts won't have restorecon.
if command -v restorecon >/dev/null 2>&1; then
    restorecon "${INDEX_PATH}" 2>/dev/null || true
fi

# ── Output ────────────────────────────────────────────────────────────────────
SIZE_BYTES=$(stat -c '%s' "${INDEX_PATH}" 2>/dev/null || wc -c < "${INDEX_PATH}" | tr -d ' ')
BACKUP_OUT="null"
[[ -n "${BACKUP_PATH}" && -f "${BACKUP_PATH}" ]] && BACKUP_OUT="${BACKUP_PATH}"

onx_log "default-page-deploy OK: user=${USERNAME} state=${STATE} path=${INDEX_PATH} bytes=${SIZE_BYTES} backup=${BACKUP_OUT}"

# Build flat JSON (onx_json_out treats "null" literal correctly via "BACKUP_OUT" branch).
if [[ "${BACKUP_OUT}" == "null" ]]; then
    onx_json_out \
        "ok"           "true" \
        "state"        "${STATE}" \
        "deployed_to"  "${INDEX_PATH}" \
        "size_bytes"   "${SIZE_BYTES}" \
        "backed_up_original" "null" \
        "skipped"      "false" \
        "skip_reason"  ""
else
    onx_json_out \
        "ok"           "true" \
        "state"        "${STATE}" \
        "deployed_to"  "${INDEX_PATH}" \
        "size_bytes"   "${SIZE_BYTES}" \
        "backed_up_original" "${BACKUP_OUT}" \
        "skipped"      "false" \
        "skip_reason"  ""
fi
