#!/usr/bin/env bash
#
# onx-mailserver-switch — Postfix ↔ Exim atomic switch (port 25 handoff).
#
# Input (stdin JSON):
#   {
#     "from":          "postfix" | "exim" | "" ,    # ops (boşsa systemd'ye sor)
#     "to":            "postfix" | "exim",          # zorunlu
#     "stop_timeout":  10,                          # ops (saniye, default 10)
#     "verify_port":   true                         # ops (default true)
#   }
#
# Output (stdout JSON):
#   {
#     "ok":              true,
#     "from":            "postfix",
#     "to":              "exim",
#     "stopped":         { "service": "postfix", "took_ms": 420 },
#     "started":         { "service": "exim",    "took_ms": 880, "active": true },
#     "port_25_holder":  "exim",
#     "message":         "Atomic switch tamamlandı: Postfix → Exim."
#   }
#
# Pattern:
#   1. Hedef driver kurulu mu? (binary check)
#   2. Hedef ZATEN aktif mi? → no-op
#   3. Source driver çalışıyorsa stop (graceful, stop_timeout içinde)
#   4. Port 25 serbest mi? (yoksa partial fail — başka servis tutuyor)
#   5. Target driver start + enable
#   6. Target driver gerçekten aktif mi? (systemctl is-active poll, max 8sn)
#   7. Port 25 holder = target driver mi? (ss/lsof verify)
#
# Pattern referansı: onx-webserver-switch (v76 strict verify mode).
#
# ÖNEMLİ: Postfix ve Exim port 25/465/587'i paylaşır. SADECE BİR TANESİ
# aktif olmalı. Switch sırasında ~500ms-2sn delivery downtime olur, retry
# queue'a düşen mailler tekrar denenir (15dk içinde Postfix/Exim retry).

set -uo pipefail

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

require_root

# ─── Input parse ─────────────────────────────────────────────────────────────
INPUT="$(cat 2>/dev/null || echo '{}')"
onx_require_json "$INPUT"

from="$(onx_json_get "$INPUT" from)"
to="$(onx_json_get "$INPUT" to)"
stop_timeout="$(onx_json_get "$INPUT" stop_timeout 10)"
verify_port_raw="$(onx_json_get_bool "$INPUT" verify_port true)"

[[ -z "$to" ]] && onx_die 1 "to driver required (postfix|exim)"

case "$to" in
    postfix|exim) ;;
    *) onx_die 1 "unknown 'to' driver '$to'" ;;
esac

# ─── 'from' boşsa systemd'den tespit et ──────────────────────────────────────
# Aynı anda iki MTA aktif olmamalı; ama emniyet için ikisini de kontrol.
if [[ -z "$from" ]]; then
    if systemctl is-active --quiet postfix 2>/dev/null; then
        from="postfix"
    elif systemctl is-active --quiet exim 2>/dev/null; then
        from="exim"
    else
        from=""   # hiçbiri çalışmıyor — fresh start
    fi
fi

# from == to → no-op
if [[ -n "$from" && "$from" == "$to" ]]; then
    onx_log "mailserver-switch no-op (from=to=${to})"
    onx_json_out \
        ok      true \
        from    "$from" \
        to      "$to" \
        message "Zaten aktif: ${to^}"
    exit 0
fi

# ─── Target binary kurulu mu? ────────────────────────────────────────────────
case "$to" in
    postfix) target_bin="/usr/sbin/postfix" ;;
    exim)    target_bin="/usr/sbin/exim" ;;
esac

if ! [[ -x "$target_bin" ]]; then
    # Debian exim4 alternatifi de varsa kabul et
    [[ "$to" == "exim" && -x "/usr/sbin/exim4" ]] || \
        onx_die 2 "Hedef MTA kurulu değil: ${to}. Önce 'mailserver-install' çalıştırın."
fi

target_service="$to"
source_service="$from"

onx_log "mailserver-switch start from=${from:-none} to=${to} timeout=${stop_timeout}"

# ─── 1) Source'u stop et (varsa ve çalışıyorsa) ──────────────────────────────
stop_took_ms=0
if [[ -n "$source_service" ]] && systemctl is-active --quiet "$source_service" 2>/dev/null; then
    stop_start_ms=$(date +%s%3N)

    # Graceful stop + timeout fallback. systemctl stop default 90sn bekler;
    # bunu stop_timeout'a kısalt. Timeout aşılırsa SIGKILL.
    timeout "${stop_timeout}" systemctl stop "$source_service" 2>/dev/null || {
        onx_log "mailserver-switch: graceful stop timeout, force kill ${source_service}"
        systemctl kill -s SIGKILL "$source_service" 2>/dev/null || true
        sleep 1
    }

    # Daemon hâlâ ayakta mı? (PID kalıntısı)
    if systemctl is-active --quiet "$source_service" 2>/dev/null; then
        onx_die 3 "Kaynak servis (${source_service}) durdurulamadı. Manuel müdahale: systemctl kill -9 ${source_service}"
    fi

    # Boot'tan da çıkar — gelecekte yanlışlıkla auto-start olmasın
    systemctl disable "$source_service" >/dev/null 2>&1 || true

    stop_end_ms=$(date +%s%3N)
    stop_took_ms=$(( stop_end_ms - stop_start_ms ))
fi

# ─── 2) Port 25 serbest mi? (verify_port=true ise) ────────────────────────────
# ss -tlnp '( sport = :25 )' → "LISTEN ... users:((...))"
if [[ "$verify_port_raw" == "true" ]]; then
    # Kısa bekleme — kernel TIME_WAIT'i kapatması için
    sleep 1

    port25_holder=""
    if command -v ss >/dev/null 2>&1; then
        port25_holder="$(ss -tlnp 2>/dev/null | awk '/:25 /{print}' | head -1)"
    fi

    if [[ -n "$port25_holder" ]]; then
        # Hâlâ tutuluyor — başka bir servis var (3rd-party MTA?). Switch güvensiz.
        onx_log "mailserver-switch ABORT: port 25 hâlâ dolu: ${port25_holder}"
        # Source'u geri başlat (rollback) — switch yapmaktan vazgeçtik
        if [[ -n "$source_service" ]]; then
            systemctl start "$source_service" >/dev/null 2>&1 || true
            systemctl enable "$source_service" >/dev/null 2>&1 || true
        fi
        onx_die 3 "Port 25 başka bir servis tarafından tutuluyor (sendmail/opensmtpd?). Switch iptal — kaynak servis yeniden başlatıldı."
    fi
fi

# ─── 3) Target'ı start et ────────────────────────────────────────────────────
start_start_ms=$(date +%s%3N)

# Enable önce — restart sırasında boot persistence garanti
systemctl enable "$target_service" >/dev/null 2>&1 || true

if ! systemctl start "$target_service" 2>/dev/null; then
    onx_log "mailserver-switch FAIL: ${target_service} start exit-nonzero"
    # Rollback: source'u geri başlat
    if [[ -n "$source_service" ]]; then
        systemctl start "$source_service" >/dev/null 2>&1 || true
        systemctl enable "$source_service" >/dev/null 2>&1 || true
    fi
    onx_die 3 "Hedef servis (${target_service}) başlatılamadı. Kaynak (${source_service:-yok}) geri yüklendi. journalctl -u ${target_service} ile log kontrol edin."
fi

# ─── 4) Active poll (max 8sn) ────────────────────────────────────────────────
active="false"
for _ in 1 2 3 4 5 6 7 8; do
    if systemctl is-active --quiet "$target_service" 2>/dev/null; then
        active="true"
        break
    fi
    sleep 1
done

start_end_ms=$(date +%s%3N)
start_took_ms=$(( start_end_ms - start_start_ms ))

if [[ "$active" != "true" ]]; then
    onx_log "mailserver-switch FAIL: ${target_service} not active after 8s"
    # Rollback
    if [[ -n "$source_service" ]]; then
        systemctl start "$source_service" >/dev/null 2>&1 || true
    fi
    onx_die 3 "Hedef servis 8 saniye içinde aktif olmadı. journalctl -u ${target_service} kontrol edin."
fi

# ─── 5) Port 25 verify (target servisi gerçekten dinliyor mu?) ───────────────
port_holder=""
if [[ "$verify_port_raw" == "true" ]] && command -v ss >/dev/null 2>&1; then
    sleep 1
    holder_pid_line="$(ss -tlnp 2>/dev/null | awk '/:25 /{print}' | head -1)"
    # users:(("master",pid=1234,fd=12)) → master = postfix; exim = exim
    if [[ -n "$holder_pid_line" ]]; then
        if [[ "$holder_pid_line" == *"\"master\""* || "$holder_pid_line" == *"\"qmgr\""* ]]; then
            port_holder="postfix"
        elif [[ "$holder_pid_line" == *"\"exim\""* ]]; then
            port_holder="exim"
        else
            # process name parse — generic
            port_holder="$(echo "$holder_pid_line" | grep -oP 'users:\(\(\"[^"]+\"' | head -1 | sed 's/users:((\"//;s/\"//')"
        fi
    fi

    if [[ -n "$port_holder" && "$port_holder" != "$to" ]]; then
        onx_log "mailserver-switch WARN: port 25 holder mismatch (got=${port_holder} expected=${to})"
        # Bu fatal değil — bazı dağıtımlarda init script proc adını farklı set ediyor.
        # Sadece warn olarak audit log'a yansıt.
    fi
fi

onx_log "mailserver-switch OK from=${from:-none} to=${to} stop_ms=${stop_took_ms} start_ms=${start_took_ms}"

# ─── Output ──────────────────────────────────────────────────────────────────
stopped_json="null"
if [[ -n "$source_service" && $stop_took_ms -gt 0 ]]; then
    stopped_json=$(printf '{"service":"%s","took_ms":%d}' "$source_service" "$stop_took_ms")
fi
started_json=$(printf '{"service":"%s","took_ms":%d,"active":true}' "$target_service" "$start_took_ms")

# Nested objects → jq ile build (onx_json_out flat-only)
jq -nc \
    --arg from           "$from" \
    --arg to             "$to" \
    --arg port_holder    "${port_holder:-unknown}" \
    --arg message        "Atomic switch tamamlandı: ${from:-none} → ${to^}." \
    --argjson stopped    "$stopped_json" \
    --argjson started    "$started_json" \
    '{ok:true, from:$from, to:$to, stopped:$stopped, started:$started,
      port_25_holder:$port_holder, message:$message}'
