#!/usr/bin/env bash
# =============================================================================
# onx-ols-default-page — OpenLiteSpeed branded CATCH-ALL (default) vhost (v3.68)
#
# Purpose:
#   OLS'de eşleşmeyen HER Host (hard-terminate edilmiş, asılı/dangling, henüz
#   provision edilmemiş, ya da tamamen bilinmeyen alan adı) için ham OLS default
#   ("Example"/"404 Not Found"/WebAdmin) sayfası yerine BRANDED bir
#   "site kullanılamıyor" sayfası gösterir.
#
#   OLS catch-all mekanizması: domain alanı tam olarak "*" olan bir Virtual Host
#   Mapping. `map <vhost> *` listener bloğuna eklenir; OLS eşleşen bir vhost
#   bulamazsa isteği bu vhost'a yönlendirir. Her listener'da YALNIZCA bir "*"
#   olabilir → idempotent: zaten bir "*" map varsa onox-default'a REPOINT edilir.
#
#   Açık domain map'i olan gerçek site'ler ETKİLENMEZ (exact-match "*"den önce
#   gelir). Panel :666 listener'ı dokunulmaz (yalnız :80 ve :443).
#
#   ACME HTTP-01 challenge context catch-all vhost'a da eklenir → henüz map'i
#   olmayan yeni domain'ler bile Let's Encrypt doğrulamasını geçebilir.
#
# Idempotent + reversible:
#   {"remove": true}  → catch-all'ı tamamen kaldırır (map + virtualhost blok +
#                        docroot + cert), httpd_config.conf'u yedekten korur.
#
# Input (stdin JSON — hepsi opsiyonel):
#   {
#     "remove":             false,
#     "reload":             true,
#     "lang":               "tr",
#     "brand_name":         "Onoxsoft Hosting",
#     "support_email":      "support@onoxsoft.com.tr",
#     "support_url":        "https://onoxsoft.com.tr/destek",
#     "brand_color":        "#6366f1",
#     "brand_color_accent": "#8b5cf6"
#   }
#
# Output (stdout JSON):
#   {"ok":true,"action":"install|remove","listeners_mapped":[..],"with_ssl":true,
#    "vhost_path":"...","docroot":"...","reloaded":true}
#
# Exit codes: 0=ok 1=invalid-input 2=preflight-fail 3=exec-fail 4=rolled-back
#
# Sudoers:
#   apache ALL=(root) NOPASSWD: /usr/local/onoxsoft/bin/onx-ols-default-page
# =============================================================================

set -uo pipefail   # -e KAPALI: best-effort adımlar (cert/render) abort etmesin

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

# ── Constants ────────────────────────────────────────────────────────────────
LSWS_BASE="/usr/local/lsws"
LISTENER_CONF="${LSWS_BASE}/conf/httpd_config.conf"
LSWS_CTL="${LSWS_BASE}/bin/lswsctrl"
VH_NAME="onox-default"
VHOST_DIR="${LSWS_BASE}/conf/vhosts/${VH_NAME}"
VHOST_PATH="${VHOST_DIR}/vhconf.conf"
BASE_DIR="${LSWS_BASE}/conf/${VH_NAME}"
HTML_DIR="${BASE_DIR}/html"
SSL_DIR="${BASE_DIR}/ssl"
CRT="${SSL_DIR}/onox-default.crt"
KEY="${SSL_DIR}/onox-default.key"
ACME_WEBROOT="/var/lib/letsencrypt/.well-known/acme-challenge"
TEMPLATES_DIR="${ONX_DEFAULT_PAGES_DIR:-/usr/local/onoxsoft/templates/default-pages}"

require_root

# ── Read & parse stdin (optional) ────────────────────────────────────────────
INPUT="$(cat 2>/dev/null || echo '{}')"
echo "$INPUT" | jq -e 'type == "object"' >/dev/null 2>&1 || INPUT='{}'

REMOVE=$(onx_json_get_bool "${INPUT}" "remove" "false")
RELOAD=$(onx_json_get_bool "${INPUT}" "reload" "true")
# prune_dangling: httpd_config.conf'taki ASILI müşteri map'lerini (vhost dizini
# silinmiş ama `map onx_<user>-<domain> ...` satırı kalmış — örn. fix'ten önce
# hard-terminate edilmiş bisaglik) temizle → bu Host'lar catch-all'a düşsün.
# Yalnız onx_* prefix'li (müşteri) vhost'lar; system/panel/Example DOKUNULMAZ.
PRUNE=$(onx_json_get_bool "${INPUT}" "prune_dangling" "false")
LANG_CODE=$(onx_json_get   "${INPUT}" "lang" "tr")
BRAND_NAME=$(onx_json_get  "${INPUT}" "brand_name"         "Onoxsoft Hosting")
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")
BRAND_COLOR_PRIMARY=$(onx_json_get "${INPUT}" "brand_color"        "#6366f1")
BRAND_COLOR_ACCENT=$(onx_json_get  "${INPUT}" "brand_color_accent" "#8b5cf6")

[[ "${LANG_CODE}" =~ ^(tr|en)$ ]] || LANG_CODE="tr"

# ── Preflight ────────────────────────────────────────────────────────────────
[[ -d "${LSWS_BASE}" ]]      || onx_die 2 "OpenLiteSpeed not installed: ${LSWS_BASE}"
[[ -x "${LSWS_CTL}" ]]       || onx_die 2 "lswsctrl not found: ${LSWS_CTL}"
[[ -f "${LISTENER_CONF}" ]]  || onx_die 2 "httpd_config.conf not found: ${LISTENER_CONF}"

LISTENER_BACKUP="${LISTENER_CONF}.bak.onox-default.$$"

# ── REMOVE path ──────────────────────────────────────────────────────────────
if [[ "${REMOVE}" == "true" ]]; then
    cp -a "${LISTENER_CONF}" "${LISTENER_BACKUP}" 2>/dev/null || true
    python3 - "${LISTENER_CONF}" "${VH_NAME}" <<'PYEOF' 2>/dev/null || true
import sys, re
conf, vh = sys.argv[1], sys.argv[2]
s = open(conf).read()
# Strip our catch-all map lines + our virtualhost block
s = re.sub(r'(?m)^[ \t]*map[ \t]+' + re.escape(vh) + r'[ \t]+\*[ \t]*$\n?', '', s)
s = re.sub(r'(?ms)^virtualhost[ \t]+' + re.escape(vh) + r'[ \t]*\{.*?^\}\n?', '', s)
open(conf, 'w').write(s)
PYEOF
    rm -rf "${VHOST_DIR}" "${BASE_DIR}" 2>/dev/null || true
    RELOADED="false"
    if [[ "${RELOAD}" == "true" ]]; then
        if "${LSWS_CTL}" restart >/dev/null 2>&1; then
            RELOADED="true"
        else
            # restart failed → restore backup
            [[ -f "${LISTENER_BACKUP}" ]] && cp -a "${LISTENER_BACKUP}" "${LISTENER_CONF}" 2>/dev/null
            "${LSWS_CTL}" restart >/dev/null 2>&1 || true
        fi
    fi
    rm -f "${LISTENER_BACKUP}" 2>/dev/null || true
    onx_log "onx-ols-default-page REMOVE done reloaded=${RELOADED}"
    onx_json_out "ok" "true" "action" "remove" "reloaded" "${RELOADED}"
    exit 0
fi

# ── INSTALL path ─────────────────────────────────────────────────────────────
mkdir -p "${HTML_DIR}" "${SSL_DIR}" "${VHOST_DIR}" 2>/dev/null

# ── 1. Render branded catch-all HTML → ${HTML_DIR}/index.html ────────────────
TMP_HTML="$(mktemp -t onx-catchall-XXXXXX.html)"
trap 'rm -f "${TMP_HTML}" 2>/dev/null' EXIT

TEMPLATE_PATH="${TEMPLATES_DIR}/catchall.html.stub"
RENDERED=0
if [[ -r "${TEMPLATE_PATH}" ]] && command -v envsubst >/dev/null 2>&1; then
    export LANG="${LANG_CODE}"
    export BRAND_NAME BRAND_COLOR_PRIMARY BRAND_COLOR_ACCENT SUPPORT_EMAIL SUPPORT_URL
    ENVSUBST_VARS='${LANG} ${BRAND_NAME} ${BRAND_COLOR_PRIMARY} ${BRAND_COLOR_ACCENT} ${SUPPORT_EMAIL} ${SUPPORT_URL}'
    if envsubst "${ENVSUBST_VARS}" < "${TEMPLATE_PATH}" > "${TMP_HTML}" 2>/dev/null \
       && grep -q "<html" "${TMP_HTML}" 2>/dev/null; then
        RENDERED=1
    fi
fi

if [[ "${RENDERED}" -eq 0 ]]; then
    # Embedded fallback (template not synced) — minimal branded page, JS host fill
    cat > "${TMP_HTML}" <<HTMLEOF
<!DOCTYPE html>
<!-- ONOX-PLACEHOLDER state=catchall template-version=1-fallback -->
<html lang="${LANG_CODE}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex,nofollow">
<title>Site su anda kullanilamiyor</title>
<style>
:root{color-scheme:light dark;--bg:#fff;--text:#111827;--muted:#6b7280;--brand:${BRAND_COLOR_PRIMARY};--accent:${BRAND_COLOR_ACCENT};--border:#e5e7eb}
@media(prefers-color-scheme:dark){:root{--bg:#0a0a0a;--text:#fafafa;--muted:#a1a1aa;--border:#27272a}}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem;line-height:1.6}
.card{max-width:460px;text-align:center}
.badge{width:64px;height:64px;border-radius:16px;margin:0 auto 1.5rem;background:linear-gradient(135deg,var(--brand),var(--accent));display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;font-weight:700}
h1{font-size:1.6rem;font-weight:700;letter-spacing:-.02em;margin-bottom:.75rem}
p{color:var(--muted);margin-bottom:1rem}
.host{font-family:ui-monospace,Menlo,Consolas,monospace;color:var(--text);font-weight:600;word-break:break-all}
.cta{display:inline-block;margin-top:.5rem;padding:.7rem 1.2rem;background:var(--brand);color:#fff;border-radius:8px;text-decoration:none;font-weight:500}
.foot{margin-top:2rem;font-size:.8rem;color:var(--muted)}
.foot strong{color:var(--brand)}
</style>
</head>
<body>
<div class="card">
<div class="badge">!</div>
<h1>Site su anda yayinda degil</h1>
<p>Aradiginiz <span class="host" id="onx-host">bu alan adi</span> su anda aktif bir siteye bagli degil. Yeni eklendiyse yayina alinmasi birkac dakika surebilir.</p>
<p style="font-size:.9rem">This site is not available right now. If the domain was added recently it may take a few minutes to go live.</p>
<a class="cta" href="${SUPPORT_URL}">Destek al</a>
<div class="foot">Powered by <strong>${BRAND_NAME}</strong></div>
</div>
<script>(function(){try{var h=location.hostname;if(h){var e=document.getElementById('onx-host');if(e)e.textContent=h;document.title=h+' · Site kullanilamiyor';}}catch(e){}})();</script>
</body>
</html>
HTMLEOF
fi

install -m 0644 "${TMP_HTML}" "${HTML_DIR}/index.html" 2>/dev/null \
    || { cp "${TMP_HTML}" "${HTML_DIR}/index.html" && chmod 0644 "${HTML_DIR}/index.html"; }

# ── 2. Self-signed default cert (for HTTPS :443 catch-all via SNI fallback) ──
WITH_SSL=0
if command -v openssl >/dev/null 2>&1; then
    if [[ ! -s "${CRT}" || ! -s "${KEY}" ]]; then
        openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
            -keyout "${KEY}" -out "${CRT}" -subj "/CN=onoxsoft-default" \
            >/dev/null 2>&1 || true
    fi
    if [[ -s "${CRT}" && -s "${KEY}" ]]; then
        WITH_SSL=1
        chmod 0600 "${KEY}" 2>/dev/null || true
        chmod 0644 "${CRT}" 2>/dev/null || true
    fi
fi

# ── 3. Write catch-all vhost conf ────────────────────────────────────────────
OLS_SSL_BLOCK=""
if [[ "${WITH_SSL}" -eq 1 ]]; then
    OLS_SSL_BLOCK="vhssl  {
  keyFile                 ${KEY}
  certFile                ${CRT}
  certChain               0
  sslProtocol             24
  enableECDHE             1
  renegProtection         1
  sslSessionCache         1
}"
fi

cat > "${VHOST_PATH}" <<VHEOF
# ONOX-managed OpenLiteSpeed CATCH-ALL (default) vhost — DO NOT edit manually.
# Generated by onx-ols-default-page. Serves a branded "site unavailable" page
# for every Host that has no explicit vhost map (terminated / dangling / unknown
# / not-yet-provisioned). Mapped via \`map ${VH_NAME} *\` on :80 and :443 listeners.

docRoot                   ${HTML_DIR}/
vhDomain                  *
adminEmails               root@localhost
enableGzip                1

errorlog ${LSWS_BASE}/logs/onox-default.error.log {
  useServer               1
  logLevel                ERROR
  rollingSize             5M
  keepDays                7
}

index  {
  useServer               0
  indexFiles              index.html
}

# ACME HTTP-01 challenge — henüz map'i olmayan yeni domain'ler bile LE doğrulasın.
context /.well-known/acme-challenge {
  location                ${ACME_WEBROOT}
  allowBrowse             1
  indexFiles              -
  rewrite  {
    enable                0
  }
  accessControl  {
    allow                 *
  }
  enableExpires           0
  expires                 -1
}

context / {
  location                ${HTML_DIR}/
  allowBrowse             1
  indexFiles              index.html
  rewrite  {
    enable                1
    inherit               0
    rules                 <<<END_RULES
RewriteCond %{REQUEST_URI} !^/index\.html\$
RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/
RewriteRule ^(.*)\$ /index.html [L]
END_RULES
  }
}

${OLS_SSL_BLOCK}
VHEOF

# ── 4. Permissions ───────────────────────────────────────────────────────────
chown -R lsadm:lsadm "${BASE_DIR}" "${VHOST_DIR}" 2>/dev/null || true
# Worker (apache:webserver) static dosyayı okuyabilsin — diğer OLS vhost'larla aynı pattern
chown -R apache:webserver "${HTML_DIR}" 2>/dev/null || true
chmod 0644 "${HTML_DIR}/index.html" 2>/dev/null || true
[[ "${WITH_SSL}" -eq 1 ]] && chmod 0600 "${KEY}" 2>/dev/null || true

# ── 5. Inject virtualhost block + catch-all maps into httpd_config.conf ──────
cp -a "${LISTENER_CONF}" "${LISTENER_BACKUP}" 2>/dev/null || true

# 5a. (opt-in) Asılı (orphaned) müşteri map'lerini temizle — Linux user'ı SİLİNMİŞ
#     (terminate edilmiş) ama `map onx_<user>-<domain> ...` + virtualhost bloğu
#     httpd_config.conf'ta KALMIŞ vhost'lar. Açık map eşleşince OLS eksik docroot'a
#     route eder (ham default) ve catch-all'ı GÖLGELER → strip et, Host `*`'a düşsün.
#     Discriminator: Linux user var mı (pwd.getpwnam). Aktif + askıdaki hesaplar
#     user'ını korur → DOKUNULMAZ; sadece userdel edilmiş hesaplar (bisaglik) temizlenir.
#     Yalnız onx_<id>-<domain> (müşteri); system/panel/onox-default ASLA eşleşmez.
if [[ "${PRUNE}" == "true" ]]; then
    PRUNED_LIST=$(python3 - "${LISTENER_CONF}" <<'PYEOF' 2>/dev/null || true
import sys, re, pwd
conf = sys.argv[1]
s = open(conf).read()
vhs = sorted(set(re.findall(r'(?m)^[ \t]*map[ \t]+(onx_[A-Za-z0-9]+-[^ \t]+)[ \t]', s)))
def user_gone(vh):
    u = vh.split('-', 1)[0]
    if not u.startswith('onx_'):
        return False
    try:
        pwd.getpwnam(u)
        return False
    except KeyError:
        return True
removed = []
for vh in vhs:
    if user_gone(vh):
        s = re.sub(r'(?m)^[ \t]*map[ \t]+' + re.escape(vh) + r'[ \t].*$\n?', '', s)
        s = re.sub(r'(?ms)^[ \t]*virtualhost[ \t]+' + re.escape(vh) + r'[ \t]*\{.*?^\}\n?', '', s)
        removed.append(vh)
open(conf, 'w').write(s)
print(",".join(removed))
PYEOF
)
    if [[ -n "${PRUNED_LIST}" ]]; then
        IFS=',' read -ra _pvhs <<< "${PRUNED_LIST}"
        for _pv in "${_pvhs[@]}"; do
            # path-traversal guard: yalnız beklenen onx_<id>-<domain> formatı
            if [[ "${_pv}" =~ ^onx_[A-Za-z0-9]+-[A-Za-z0-9._-]+$ ]]; then
                rm -rf "${LSWS_BASE}/conf/vhosts/${_pv}" 2>/dev/null || true
            fi
        done
        onx_log "onx-ols-default-page prune removed (user-gone): ${PRUNED_LIST}"
    fi
fi

# Fresh virtualhost block (apache:webserver, static-only — enableScript 0)
VH_BLOCK_FILE="$(mktemp -t onx-vhblock-XXXXXX)"
cat > "${VH_BLOCK_FILE}" <<VHBEOF

virtualhost ${VH_NAME} {
  vhRoot                  ${VHOST_DIR}/
  configFile              ${VHOST_PATH}
  allowSymbolLink         1
  enableScript            0
  restrained              0
  setUIDMode              2
  user                    apache
  group                   webserver
}
VHBEOF

python3 - "${LISTENER_CONF}" "${VH_NAME}" "${WITH_SSL}" "${VH_BLOCK_FILE}" <<'PYEOF' 2>/dev/null
import sys, re

conf, vh, with_ssl_s, blockfile = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
with_ssl = (with_ssl_s == "1")
with open(conf) as f:
    s = f.read()
with open(blockfile) as f:
    vhblock = f.read()

# 1) Remove any existing onox-default virtualhost block (refresh content)
s = re.sub(r'(?ms)^[ \t]*virtualhost[ \t]+' + re.escape(vh) + r'[ \t]*\{.*?^\}\n?', '', s)

# 2) Append fresh virtualhost block at EOF
if not s.endswith("\n"):
    s += "\n"
s = s + vhblock.lstrip("\n") + "\n"

# 3) Catch-all map injection per :80 / :443 listener
ports_ok = {80}
if with_ssl:
    ports_ok.add(443)
map_line = "\n  map                     %s *" % vh

# listener blocks have NO nested braces in OLS httpd_config.conf
pat = re.compile(r'(listener\s+(\w+)\s*\{)([^}]*?)(\n\s*\})', re.DOTALL)
out, last = [], 0
mapped = []
for m in pat.finditer(s):
    name, body, tail = m.group(2), m.group(3), m.group(4)
    addr = re.search(r'address\s+\S*?:(\d+)', body)
    if not addr:
        continue
    port = int(addr.group(1))
    if port not in ports_ok:
        continue
    # already mapped to us?
    if re.search(r'(?m)^[ \t]*map[ \t]+' + re.escape(vh) + r'[ \t]+\*[ \t]*$', body):
        mapped.append(name)
        continue
    # existing pure catch-all (map <X> *) → repoint to us
    new_body, n = re.subn(
        r'(?m)^([ \t]*map[ \t]+)\S+([ \t]+\*[ \t]*)$',
        r'\g<1>' + vh + r'\g<2>', body)
    out.append(s[last:m.start(3)])
    if n > 0:
        out.append(new_body)
    else:
        out.append(body + map_line)
    last = m.end(3)
    mapped.append(name)
out.append(s[last:])
s = ''.join(out)

with open(conf, 'w') as f:
    f.write(s)

# Emit mapped listener names (one per line) to stderr for the caller's log
sys.stderr.write("MAPPED:" + ",".join(mapped) + "\n")
PYEOF
PY_RC=$?
rm -f "${VH_BLOCK_FILE}" 2>/dev/null || true

if [[ "${PY_RC}" -ne 0 ]]; then
    # Python edit failed → restore + abort (no restart)
    [[ -f "${LISTENER_BACKUP}" ]] && cp -a "${LISTENER_BACKUP}" "${LISTENER_CONF}" 2>/dev/null
    rm -f "${LISTENER_BACKUP}" 2>/dev/null || true
    onx_die 3 "failed to inject catch-all map into httpd_config.conf"
fi

# ── 6. lswsctrl restart (rollback listener conf on failure) ──────────────────
RELOADED="false"
if [[ "${RELOAD}" == "true" ]]; then
    if "${LSWS_CTL}" restart >/dev/null 2>&1; then
        RELOADED="true"
    else
        onx_log "onx-ols-default-page: lswsctrl restart failed — rolling back listener conf"
        [[ -f "${LISTENER_BACKUP}" ]] && cp -a "${LISTENER_BACKUP}" "${LISTENER_CONF}" 2>/dev/null
        if "${LSWS_CTL}" restart >/dev/null 2>&1; then
            rm -f "${LISTENER_BACKUP}" 2>/dev/null || true
            onx_die 4 "lswsctrl restart failed; listener conf rolled back (vhost files kept)"
        fi
        rm -f "${LISTENER_BACKUP}" 2>/dev/null || true
        onx_die 3 "lswsctrl restart failed AND rollback restart failed"
    fi
fi
rm -f "${LISTENER_BACKUP}" 2>/dev/null || true

onx_log "onx-ols-default-page INSTALL ok with_ssl=${WITH_SSL} reloaded=${RELOADED}"

onx_json_out \
    "ok"          "true" \
    "action"      "install" \
    "with_ssl"    "$([[ ${WITH_SSL} -eq 1 ]] && echo true || echo false)" \
    "vhost_path"  "${VHOST_PATH}" \
    "docroot"     "${HTML_DIR}" \
    "reloaded"    "${RELOADED}"
exit 0
