#!/usr/bin/env bash
#
# onx-exim-conf-deploy — Atomic Exim conf deploy (template-based, v91)
#
# v90 fact-check (feedback_onoxsoft_exim_conf_regex_patch.md):
#   v90 onx-exim-dkim-regenerate Python regex ile exim.conf'a DKIM block inject
#   ediyordu → duplicate `dkim_domain` directive → production Exim DOWN → RPM
#   reinstall ile vanilla state. Onoxsoft özellikleri (DKIM, Dovecot SASL,
#   virtual_domains, Rspamd ACL, TLS, fail2ban) TAMAMEN kayboldu.
#
# v91 doğru pattern:
#   - PHP tarafı (EximConfBuilder) TAM dosya template render eder
#   - Bu sysapi tam dosyayı stdin ile alır
#   - Atomik akış: backup → temp yazımı → `exim -C temp -bV` → mv -f → reload
#   - Hata: temp temizlenir, production exim.conf değişmez
#   - Reload başarısız: backup'tan restore + systemctl start
#
# Tarih: 2026-05 (v91 Agent 1)
#
# stdin:
#   {
#     "content": "<full exim.conf content (UTF-8, LF)>",
#     "skip_reload": false        // (optional) true → sadece dosya yaz, reload etme
#   }
#
# stdout (success): {
#   "ok": true,
#   "path": "/etc/exim/exim.conf",
#   "backup": "/etc/exim/exim.conf.bak.v91.1716800000",
#   "bytes_written": 8521,
#   "syntax_test": "ok",
#   "reloaded": true,
#   "active_after": true,
#   "version": "Exim version 4.96 ..."
# }
#
# stderr on failure: {"error":"...","detail":"..."} + non-zero exit
#
# Exit codes:
#   0 — success (yazıldı + syntax OK + reload OK)
#   1 — input validation fail (boş/eksik content)
#   2 — preflight fail (exim binary yok, conf dosyası yazılabilir değil)
#   3 — syntax test fail (exim -C ... -bV non-zero, üretim dokunulmadı)
#   4 — reload/restart fail + backup'tan rollback yapıldı
#   5 — reload fail + rollback DA fail (kritik, panel manuel müdahale)

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

require_root
require_cmd jq

onx_json_input

# ─── Input parsing ────────────────────────────────────────────────────────────
CONTENT=$(echo "$INPUT" | jq -r '.content // ""')
SKIP_RELOAD=$(onx_json_get_bool "$INPUT" "skip_reload" "false")

# Boş veya çok kısa içeriğe izin verme — Exim'i yanlışlıkla boşaltma engeli
if [[ -z "$CONTENT" || ${#CONTENT} -lt 100 ]]; then
    onx_die 1 "icerik bos veya cok kisa (min 100 byte) — exim.conf bosaltilamaz"
fi

# Max 1MB — Exim conf normalde 8-50KB, 1MB üst sınır olarak fazlasıyla geniş
MAX_BYTES=1048576
if [[ ${#CONTENT} -gt $MAX_BYTES ]]; then
    onx_die 1 "icerik cok buyuk (max 1MB, gelen: ${#CONTENT})"
fi

# ─── Conf path detection ──────────────────────────────────────────────────────
CONF_PATH=""
for p in /etc/exim/exim.conf /etc/exim4/exim4.conf; do
    if [[ -f "$p" ]]; then
        CONF_PATH="$p"
        break
    fi
done

# Conf dosyası yoksa Exim henüz install edilmemiş demektir — installer akışı
# bunu yaratmadan deploy çağırmamalı; defansif hata fırlat.
if [[ -z "$CONF_PATH" ]]; then
    onx_die 2 "exim.conf bulunamadi — Exim kurulu mu? (mailserver-install onx-mta-* once)"
fi

# Conf dosyası yazılabilir mi?
CONF_DIR=$(dirname "$CONF_PATH")
if [[ ! -w "$CONF_DIR" ]]; then
    onx_die 2 "exim.conf dizini yazilabilir degil: $CONF_DIR (root yetkisi var ama mount RO mu?)"
fi

# ─── Exim binary detection ────────────────────────────────────────────────────
EXIM_BIN=""
for bin in /usr/sbin/exim /usr/sbin/exim4 /usr/bin/exim; do
    if [[ -x "$bin" ]]; then
        EXIM_BIN="$bin"
        break
    fi
done

if [[ -z "$EXIM_BIN" ]]; then
    onx_die 2 "exim binary bulunamadi (PATH: $PATH)"
fi

# ─── 1) Backup ────────────────────────────────────────────────────────────────
TS=$(date +%s)
BACKUP_PATH="${CONF_PATH}.bak.v91.${TS}"

if ! cp -a "$CONF_PATH" "$BACKUP_PATH" 2>/dev/null; then
    onx_die 3 "backup olusturulamadi: $BACKUP_PATH"
fi

onx_log "v91 deploy backup: $BACKUP_PATH ($(stat -c%s "$BACKUP_PATH" 2>/dev/null || echo 0) byte)"

# Eski v91 backup'larını rotate et — son 10 backup'ı tut
# (Production'da 100+ backup birikmesin diye)
# shellcheck disable=SC2012
ls -1t "${CONF_PATH}.bak.v91."* 2>/dev/null | tail -n +11 | while IFS= read -r old_bak; do
    rm -f "$old_bak" 2>/dev/null || true
done

# ─── 2) Yeni içerik temp dosyaya yaz (atomik mv için aynı dizinde) ────────────
TMP_NEW=$(mktemp -p "$CONF_DIR" ".exim.conf.new.XXXXXX")
if [[ -z "$TMP_NEW" ]]; then
    onx_die 3 "mktemp basarisiz: $CONF_DIR"
fi

# Cleanup: temp dosyayı her durumda sil (success'te de — atomic mv sonrası
# zaten dosya artık conf path'ine taşınmış olacak; başarısız akışta temp kalıntı
# bırakmamak için)
# shellcheck disable=SC2064
trap "rm -f '$TMP_NEW' 2>/dev/null || true" EXIT

# Orijinal conf'un mode + ownership'ini öğren (yeni dosyaya aynısını uygula)
ORIG_MODE=$(stat -c%a "$CONF_PATH" 2>/dev/null || echo "0644")
ORIG_OWNER=$(stat -c%U:%G "$CONF_PATH" 2>/dev/null || echo "root:root")

printf '%s' "$CONTENT" > "$TMP_NEW"
chmod "$ORIG_MODE" "$TMP_NEW" 2>/dev/null || true
chown "$ORIG_OWNER" "$TMP_NEW" 2>/dev/null || true

BYTES_WRITTEN=$(stat -c%s "$TMP_NEW" 2>/dev/null || echo "${#CONTENT}")

# ─── 3) Syntax test — exim -C <temp> -bV ──────────────────────────────────────
# -C  → alternatif config dosyası kullan (production'a dokunmaz)
# -bV → version + macro + ana config parse check (syntax dahil)
# Exim güvenlik: -C ile alternatif conf kullanmak sadece exim user / root için
# izinlidir; biz root altındayız, sorun yok.
if ! BV_OUT=$("$EXIM_BIN" -C "$TMP_NEW" -bV 2>&1); then
    BV_RC=$?
    # Üretim conf'a DOKUNMADIK — sadece temp temizlenir (trap halleder)
    # İlk 5 satır error msg'i panel'e döndür (UI'da göstermek için)
    DETAIL=$(printf '%s' "$BV_OUT" | head -10 | tr '\n' ' ' | head -c 800)
    DETAIL_JSON=$(printf '%s' "$DETAIL" | jq -Rs '.')
    BACKUP_JSON=$(printf '%s' "$BACKUP_PATH" | jq -Rs '.')
    {
        printf '{"ok":false,"error":"exim -bV syntax check basarisiz (uretim dokunulmadi)",'
        printf '"detail":%s,"exit_code":%d,"backup":%s}\n' "$DETAIL_JSON" "$BV_RC" "$BACKUP_JSON"
    } >&2
    exit 3
fi

# Version'i çıkar (örn "Exim version 4.96 #1 built ...")
VERSION=$(printf '%s\n' "$BV_OUT" | grep -oE 'Exim version [0-9.]+' | head -1 || echo "Exim (unknown)")

# ─── 4) Atomik mv — temp → production ─────────────────────────────────────────
# Rename syscall POSIX atomic — power-loss / crash durumunda partial write yok
if ! mv -f "$TMP_NEW" "$CONF_PATH"; then
    onx_die 3 "atomik mv basarisiz: $TMP_NEW → $CONF_PATH"
fi

# Trap'i temizle (artık dosya prod path'inde, temp adı boş)
trap - EXIT

onx_log "v91 deploy yazildi: $CONF_PATH ($BYTES_WRITTEN byte), version=$VERSION"

# ─── 5) Reload (skip_reload=true ise atla) ────────────────────────────────────
RELOADED="false"
ACTIVE_AFTER="false"
ROLLBACK_DONE="false"

if [[ "$SKIP_RELOAD" == "true" ]]; then
    # Sadece dosya yazıldı — caller manuel reload yapacak (ör batch deploy)
    if systemctl is-active --quiet exim 2>/dev/null; then
        ACTIVE_AFTER="true"
    fi
    onx_log "v91 deploy skip_reload=true — reload atlandi"
else
    # Exim reload sinyali yok (postfix farklı) — sadece restart geçerli
    # Exim ana process kendi config'ini parse eder ve listener'ı yeniden açar
    # → reload yerine restart. Down-time ~1-2 saniye (queue runner kesintisiz).
    if systemctl restart exim 2>/dev/null; then
        # Restart sonrası systemd active state stabilize olsun (race önlemi)
        sleep 1
        if systemctl is-active --quiet exim 2>/dev/null; then
            RELOADED="true"
            ACTIVE_AFTER="true"
            onx_log "v91 deploy reload OK — exim active"
        else
            # Restart geçti ama active değil — config kabul edildi ama port
            # binding fail olabilir (örn 25 başka process tarafından tutuluyor)
            # Backup'tan restore + start dene
            onx_log "v91 deploy: exim restart sonrasi inactive — rollback baslatiyor"
            if cp -a "$BACKUP_PATH" "$CONF_PATH" 2>/dev/null \
                && systemctl restart exim 2>/dev/null \
                && sleep 1 \
                && systemctl is-active --quiet exim 2>/dev/null; then
                ROLLBACK_DONE="true"
                RELOADED="false"
                ACTIVE_AFTER="true"
                # Detail msg ile fail dön — caller bilsin
                DETAIL=$(systemctl status exim --no-pager -l 2>&1 | tail -20 | tr '\n' ' ' | head -c 600)
                DETAIL_JSON=$(printf '%s' "$DETAIL" | jq -Rs '.')
                BACKUP_JSON=$(printf '%s' "$BACKUP_PATH" | jq -Rs '.')
                {
                    printf '{"ok":false,"error":"reload sonrasi inactive — rollback yapildi",'
                    printf '"detail":%s,"backup":%s,"rollback_done":true}\n' "$DETAIL_JSON" "$BACKUP_JSON"
                } >&2
                exit 4
            else
                # Rollback DA başarısız — KRİTİK (panel manuel müdahale gerek)
                onx_log "v91 deploy CRITICAL: rollback fail — exim DOWN"
                BACKUP_JSON=$(printf '%s' "$BACKUP_PATH" | jq -Rs '.')
                {
                    printf '{"ok":false,"error":"reload fail + rollback fail (KRITIK)",'
                    printf '"backup":%s,"rollback_done":false}\n' "$BACKUP_JSON"
                } >&2
                exit 5
            fi
        fi
    else
        # Restart çağrısı bile fail — service unit yok veya systemd sorunlu
        # Backup'tan restore + start
        onx_log "v91 deploy: systemctl restart exim fail — rollback baslatiyor"
        if cp -a "$BACKUP_PATH" "$CONF_PATH" 2>/dev/null; then
            ROLLBACK_DONE="true"
            systemctl restart exim 2>/dev/null || true
            sleep 1
            if systemctl is-active --quiet exim 2>/dev/null; then
                ACTIVE_AFTER="true"
            fi
            BACKUP_JSON=$(printf '%s' "$BACKUP_PATH" | jq -Rs '.')
            {
                printf '{"ok":false,"error":"systemctl restart exim fail — rollback yapildi",'
                printf '"backup":%s,"rollback_done":true,"active_after":%s}\n' "$BACKUP_JSON" "$ACTIVE_AFTER"
            } >&2
            exit 4
        else
            BACKUP_JSON=$(printf '%s' "$BACKUP_PATH" | jq -Rs '.')
            {
                printf '{"ok":false,"error":"restart fail + rollback fail (KRITIK)",'
                printf '"backup":%s,"rollback_done":false}\n' "$BACKUP_JSON"
            } >&2
            exit 5
        fi
    fi
fi

# ─── 6) Output JSON (success) ─────────────────────────────────────────────────
onx_json_out \
    ok true \
    path "$CONF_PATH" \
    backup "$BACKUP_PATH" \
    bytes_written "$BYTES_WRITTEN" \
    syntax_test "ok" \
    reloaded "$RELOADED" \
    active_after "$ACTIVE_AFTER" \
    rollback_done "$ROLLBACK_DONE" \
    version "$VERSION"

exit 0
