#!/usr/bin/env bash
#
# onx-exim-dkim-regenerate — Exim outbound DKIM signing config regen
#
# v90 Agent 1: P0.2 fix — DKIM key Rspamd'a deploy ediliyor ama Exim outbound
# için signing config eski "default" selector'i hardcode kullanıyordu.
# DkimManagerController custom selector ("onox2026", rotated "y2026-NN") üretince
# Exim hâlâ /etc/onox/dkim/<domain>/default.private arıyordu → bulamayıp imzasız
# gönderiyordu → Gmail/Yahoo dkim=fail → spam folder.
#
# Bu script:
#   1) /etc/exim/dkim_selectors lsearch lookup file yazar (domain: selector)
#   2) exim.conf'taki remote_smtp transport DKIM block'unu lookup pattern'ına
#      geçirir (idempotent — marker `# v90:`)
#   3) exim -bV syntax check, fail ise rollback (.bak.v90.<ts>)
#   4) systemctl reload exim
#
# Lookup pattern (Exim string expansion):
#   dkim_selector = ${lookup{$sender_address_domain} lsearch{/etc/exim/dkim_selectors} {$value}{default}}
#
# Bu sayede aynı sunucudaki farklı domain'ler farklı selector kullanabilir:
#   example.com    → onox2026
#   acme.com.tr    → y2026
#   newkid.com     → default (lookup miss = default fallback)
#
# stdin:
#   {
#     "entries": [
#       {"domain": "example.com", "selector": "onox2026"},
#       {"domain": "acme.com.tr", "selector": "y2026"}
#     ],
#     "default_selector": "default"
#   }
#
# stdout: {"ok":true,"map_path":"/etc/exim/dkim_selectors","entry_count":N,"reloaded":true}

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

require_root
require_cmd jq

onx_json_input

EXIM_CONF="/etc/exim/exim.conf"
MAP_FILE="/etc/exim/dkim_selectors"

# Exim kurulu mu? Kurulu değilse no-op (panel henüz Exim install etmemiş olabilir).
if [[ ! -f "$EXIM_CONF" ]]; then
    onx_json_out \
        ok true \
        skipped true \
        reason "exim.conf yok — Exim henüz kurulu değil" \
        map_path "$MAP_FILE" \
        entry_count 0 \
        reloaded false
    exit 0
fi

DEFAULT_SELECTOR=$(onx_json_field "default_selector" "default")
# Selector sanity (alphanumerik + tire/alt çizgi)
if ! [[ "$DEFAULT_SELECTOR" =~ ^[a-zA-Z0-9_-]+$ ]]; then
    onx_die 1 "Geçersiz default_selector formatı: $DEFAULT_SELECTOR"
fi

# Entries JSON array'i jq ile parse et — her satır "domain: selector"
# format'ında lookup file'a yazılır (Exim lsearch native pattern).
ENTRY_LINES=$(echo "$INPUT" | jq -r '
    if (.entries // null) | type == "array" then
        .entries[]
        | select((.domain // "") != "" and (.selector // "") != "")
        | "\(.domain): \(.selector)"
    else
        empty
    end
' 2>/dev/null)

ENTRY_COUNT=0
if [[ -n "$ENTRY_LINES" ]]; then
    # Domain + selector validation (her satır için)
    while IFS= read -r line; do
        [[ -z "$line" ]] && continue
        # Format: "domain: selector"
        dom="${line%%:*}"
        sel="${line##*: }"
        # Domain RFC 1035-lite check
        if ! [[ "$dom" =~ ^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$ ]]; then
            onx_die 1 "Geçersiz domain: $dom"
        fi
        # Selector format check
        if ! [[ "$sel" =~ ^[a-zA-Z0-9_-]+$ ]]; then
            onx_die 1 "Geçersiz selector: $sel (domain=$dom)"
        fi
        ENTRY_COUNT=$((ENTRY_COUNT + 1))
    done <<< "$ENTRY_LINES"
fi

# ─── Lookup file yaz ─────────────────────────────────────────────────────────
# Format: domain: selector\n
# Boş giriş listesi panic guard — sadece header yazarız ki Exim lookup
# fail-graceful'a düşsün (default fallback aktif kalır).
mkdir -p "$(dirname "$MAP_FILE")"
{
    printf '# /etc/exim/dkim_selectors\n'
    printf '# v90 Agent 1: Per-domain DKIM signing selector map\n'
    printf '# Panel auto-generated — onx-exim-dkim-regenerate çağrısı ile güncellenir.\n'
    printf '# DO NOT EDIT MANUALLY.\n'
    printf '# Generated: %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
    printf '# Entries: %d\n' "$ENTRY_COUNT"
    printf '#\n'
    if [[ -n "$ENTRY_LINES" ]]; then
        printf '%s\n' "$ENTRY_LINES"
    fi
} > "${MAP_FILE}.tmp"
chmod 644 "${MAP_FILE}.tmp"
mv -f "${MAP_FILE}.tmp" "$MAP_FILE"

# ─── v90.3 — exim.conf patch DEVRE DIŞI (safety) ──────────────────────────────
# v90 Agent 1 sysapi exim.conf'a Python regex patch yapıyordu — production'da
# lookup file CONTENT'i exim.conf'a sızıyor (regex offset bug) ve exim -bV fail.
# Pragmatik yaklaşım: sysapi SADECE map file (/etc/exim/dkim_selectors) yazsın.
# Mevcut exim.conf'taki hardcoded `dkim_selector = default` lookup pattern'ına
# admin manuel olarak (veya sonraki sprintte temiz template ile) çevirir.
#
# Multi-selector rotation şu an critical değil (1 active DKIM key) — backlog.
# Lookup file yaz, EXIM_CONF'a DOKUNMA — `if false` ile branch disabled.

CONF_PATCHED="false"
CONF_PATCH_HINT="exim.conf manuel patch gerekli: dkim_selector = \${lookup{\$sender_address_domain}lsearch{${MAP_FILE}}{\$value}{${DEFAULT_SELECTOR}}}"

if false; then
    # v90.0 Python patch — DISABLED (regex offset bug, exim.conf sızıyor)
    NEED_CONF_PATCH=1
    if grep -q "^# v90: Per-domain DKIM signing" "$EXIM_CONF" 2>/dev/null; then
        NEED_CONF_PATCH=0
    fi
fi

if false; then  # v90.3: Python patch tamamen disabled — branch unreachable
    # Backup
    BACKUP_FILE="${EXIM_CONF}.bak.v90.$(date +%s)"
    cp -a "$EXIM_CONF" "$BACKUP_FILE"
    onx_log "Backup created: $BACKUP_FILE"

    # Python ile transport patch — v84.4 marker'ı varsa onu rewrite et,
    # yoksa v90 marker'ı ile yeni block ekle.
    python3 - "$EXIM_CONF" "$DEFAULT_SELECTOR" "$MAP_FILE" <<'PYEOF'
import re
import sys

CONF = sys.argv[1]
DEFAULT_SELECTOR = sys.argv[2]
MAP_FILE = sys.argv[3]

with open(CONF) as f:
    text = f.read()

# v90 DKIM block — lookup pattern (per-domain selector support)
# v90.2 fix: lookup'da $sender_address_domain kullan (dkim_domain henüz set olmamış olabilir
# expansion order'ında). Sender domain her zaman valid + outbound context'te mevcut.
dkim_block_v90 = f'''
    # v90: Per-domain DKIM signing — lookup file /etc/exim/dkim_selectors
    # Format: "<domain>: <selector>\\n". Lookup miss → default selector fallback.
    # Map yöneticisi: onx-exim-dkim-regenerate (DkimService::generateKey/removeKey hook).
    dkim_domain = ${{sender_address_domain}}
    dkim_selector = ${{lookup{{$sender_address_domain}}lsearch{{{MAP_FILE}}}{{$value}}{{{DEFAULT_SELECTOR}}}}}
    dkim_private_key = ${{if exists{{/etc/onox/dkim/${{sender_address_domain}}/${{dkim_selector}}.private}}{{/etc/onox/dkim/${{sender_address_domain}}/${{dkim_selector}}.private}}{{}}}}
    dkim_canon = relaxed
    dkim_strict = 0'''

# remote_smtp transport pattern: "remote_smtp:\n  driver = smtp" + sonraki settings satırları
pattern = re.compile(
    r'(remote_smtp:\s*\n\s*driver\s*=\s*smtp\s*\n)((?:\s+[a-z_]+\s*=.*\n)*)',
    re.MULTILINE
)

m = pattern.search(text)
if not m:
    print("ERROR: remote_smtp transport pattern not found in exim.conf", file=sys.stderr)
    sys.exit(2)

# Mevcut DKIM satırlarını sil (v84.4 hardcoded + eski v90 olabilir — re-run safe)
existing = m.group(2)
cleaned = re.sub(
    r'^\s*dkim_(?:domain|selector|private_key|canon|strict)\s*=.*\n',
    '',
    existing,
    flags=re.MULTILINE
)
new_block = m.group(1) + cleaned.rstrip() + '\n' + dkim_block_v90 + '\n'
new_text = text[:m.start()] + new_block + text[m.end():]

# Üst-satır marker (v90)
if not re.search(r'^# v90: Per-domain DKIM signing', new_text, flags=re.MULTILINE):
    # v84.4 marker'ı varsa onun altına ekle; yoksa dosyanın en üstüne
    if re.search(r'^# v84\.4:', new_text, flags=re.MULTILINE):
        new_text = re.sub(
            r'(^# v84\.4:.*\n)',
            r'\1# v90: Per-domain DKIM signing map active (/etc/exim/dkim_selectors)\n',
            new_text,
            count=1,
            flags=re.MULTILINE
        )
    else:
        new_text = '# v90: Per-domain DKIM signing map active (/etc/exim/dkim_selectors)\n' + new_text

with open(CONF, 'w') as f:
    f.write(new_text)

print("OK")
PYEOF
    PY_RC=$?
    if [[ "$PY_RC" -ne 0 ]]; then
        # Python patch fail → rollback
        cp -f "$BACKUP_FILE" "$EXIM_CONF"
        onx_die 3 "exim.conf patch fail (python rc=$PY_RC) — rollback from $BACKUP_FILE"
    fi

    # Syntax check — fail ise rollback. v90.2: stderr capture, hatayı response'a ekle
    EXIM_TEST_OUT=$(exim -bV 2>&1)
    EXIM_TEST_RC=$?
    if [[ "$EXIM_TEST_RC" -ne 0 ]]; then
        cp -f "$BACKUP_FILE" "$EXIM_CONF"
        # İlk 5 satır error msg (debug için)
        EXIM_ERR_SHORT=$(echo "$EXIM_TEST_OUT" | head -5 | tr '\n' ' ' | head -c 400)
        onx_die 3 "exim -bV fail (rc=$EXIM_TEST_RC) — rollback from $BACKUP_FILE — exim_stderr: ${EXIM_ERR_SHORT}"
    fi

    CONF_PATCHED="true"
fi

# ─── Reload Exim ──────────────────────────────────────────────────────────────
# Exim reload sinyali yok; lookup file değişikliği için restart şart değil
# (lsearch her query'de dosyayı re-read eder), ama transport conf patch'i
# yapıldıysa restart gerek.
RELOADED="false"
if [[ "$CONF_PATCHED" == "true" ]]; then
    if systemctl restart exim 2>/dev/null; then
        sleep 1
        if systemctl is-active --quiet exim; then
            RELOADED="true"
        else
            onx_die 3 "Exim restart sonrası inactive — journalctl -u exim"
        fi
    else
        # systemd unit yoksa (exim henüz install edilmedi) skip
        onx_log "systemctl restart exim atlandı (unit yok)"
    fi
else
    # Sadece lookup file değişti — Exim'in lsearch cache'i yok, anlık aktif.
    # Yine de smoke için reload dene (yan etkisiz).
    if systemctl is-active --quiet exim 2>/dev/null; then
        RELOADED="true"
    fi
fi

# ─── Output ──────────────────────────────────────────────────────────────────
onx_json_out \
    ok true \
    map_path "$MAP_FILE" \
    entry_count "$ENTRY_COUNT" \
    default_selector "$DEFAULT_SELECTOR" \
    conf_patched "$CONF_PATCHED" \
    conf_patch_hint "$CONF_PATCH_HINT" \
    reloaded "$RELOADED"
exit 0
