#!/usr/bin/env bash
# onx-sieve-write — Per-mailbox Sieve script üretir + doveadm ile aktif eder.
#
# stdin:
#   {
#     "email":"alice@example.com",
#     "vmail_uid":"vmail",
#     "rules": [
#         {"type":"whitelist","sender":"foo@x.tr"},
#         {"type":"blacklist","sender":"spam@y.tr"},
#         {"type":"filter","header":"Subject","contains":"newsletter","action":"fileinto","mailbox":"Bulletin"},
#         {"type":"filter","field":"from","operator":"contains","value":"@x.com","action":"discard"},
#         {"type":"filter","field":"subject","operator":"regex","value":"^\\\\[SPAM\\\\]","action":"fileinto","mailbox":"Junk"},
#         {"type":"vacation","subject":"Tatildeyim","days":7,"message":"15 Mayıs'ta dönüyorum.","from":"Alice <alice@x.tr>"},
#         {"type":"forward","destinations":["bob@x.com","charlie@y.com"],"keep_local_copy":true}
#     ],
#     "boxtrapper":true,
#     "raw_script": "require [\"fileinto\"]; …"   (opsiyonel: raw mode advanced kullanıcı)
#   }
#
# stdout: {"ok":true,"script_path":"…","bytes":NNN,"message":"…"}
#
# Notlar:
# - Sieve script /var/vmail/<domain>/<local>/sieve/managesieve.sieve altına yazılır
#   (Dovecot Pigeonhole sieve_dir = ~/sieve).
# - rules dizisinden Sieve syntax üretir; advanced mode "raw_script" verilirse o kullanılır.
# - boxtrapper=true ise whitelist dışı tüm gelen postalar "Quarantine" klasörüne taşınır.

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

require_root
require_cmd jq

INPUT="$(cat)"
[[ -n "$INPUT" ]] || json_fail 1 "Eksik stdin"

EMAIL="$(echo "$INPUT" | jq -r '.email // ""')"
RAW="$(echo "$INPUT" | jq -r '.raw_script // ""')"
BOXTRAPPER="$(echo "$INPUT" | jq -r '.boxtrapper // false')"

[[ -n "$EMAIL" ]] || json_fail 1 "Eksik alan: email"
# >/dev/null kritik — onx_validate_email stdout'a email'i echo'luyor (caller
# command-substitution kullansa diye); biz JSON dönüyoruz, kirletmemeli.
onx_validate_email "$EMAIL" >/dev/null

DOMAIN="$ONX_EMAIL_DOMAIN"
LOCAL="$ONX_EMAIL_LOCAL"
VMAIL_BASE="/var/vmail/${DOMAIN}/${LOCAL}"

# Maildir / sieve klasörü oluştur (Dovecot ilk login'de açar ama biz garanti edelim)
SIEVE_DIR="${VMAIL_BASE}/sieve"
mkdir -p "$SIEVE_DIR"
chown -R vmail:vmail "$VMAIL_BASE" 2>/dev/null || true
chmod 700 "$SIEVE_DIR" 2>/dev/null || true

SIEVE_FILE="${SIEVE_DIR}/managesieve.sieve"
SIEVE_TMP="$(mktemp -t onx-sieve.XXXXXX)"
trap 'rm -f "$SIEVE_TMP"' EXIT

# --- Raw mode (advanced) ---
if [[ -n "$RAW" && "$RAW" != "null" ]]; then
    printf '%s\n' "$RAW" > "$SIEVE_TMP"
else
    # --- Generate from rules ---
    {
        printf '# Auto-generated by Onoxsoft on %s\n' "$(date -u +%FT%TZ)"
        printf 'require ["fileinto","envelope","vacation","imap4flags","copy","reject","regex","body","mailbox"];\n\n'

        # Helper: Sieve string escape (quote, backslash)
        sieve_escape() {
            printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
        }

        # Whitelist/blacklist rules
        WL="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="whitelist") | .sender] | @json')"
        BL="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="blacklist") | .sender] | @json')"

        # Blacklist: reject
        if [[ "$BL" != "[]" && "$BL" != "null" ]]; then
            printf '# --- Blacklist (reject) ---\n'
            echo "$INPUT" | jq -r '.rules[]? | select(.type=="blacklist") | .sender' | while read -r s; do
                [[ -n "$s" ]] || continue
                s_esc=$(sieve_escape "$s")
                printf 'if envelope :is "from" "%s" { reject "Mail not accepted."; stop; }\n' "$s_esc"
            done
            printf '\n'
        fi

        # Filter rules — DB schema: field, operator, value, action, action_value, stop_on_match
        # field: from, to, subject, body, any_header
        # operator: contains, equals, starts_with, ends_with, regex, not_contains
        # action: discard, fail, redirect, save_to_folder, forward, reject
        printf '# --- Filter rules (per-mailbox custom) ---\n'
        echo "$INPUT" | jq -c '.rules[]? | select(.type=="filter")' | while read -r row; do
            [[ -n "$row" ]] || continue

            # Yeni schema fields (öncelikli) + eski schema fallback
            FIELD="$(echo "$row" | jq -r '.field // .header // "subject"')"
            OP="$(echo "$row" | jq -r '.operator // "contains"')"
            VAL="$(echo "$row" | jq -r '.value // .contains // ""')"
            ACT="$(echo "$row" | jq -r '.action // "fileinto"')"
            ACT_VAL="$(echo "$row" | jq -r '.action_value // .mailbox // "INBOX"')"
            STOP="$(echo "$row" | jq -r '.stop // true')"

            [[ -n "$VAL" ]] || continue
            VAL_ESC=$(sieve_escape "$VAL")
            ACT_VAL_ESC=$(sieve_escape "$ACT_VAL")

            # Field → Sieve match expression
            # body için body test; diğerleri header test
            case "$FIELD" in
                body)     COND_PREFIX='body :text' ;;
                from|to|subject|cc|bcc|reply-to)
                          COND_PREFIX="header" ;;
                any_header|*)
                          COND_PREFIX='header' ;;
            esac

            # Operator → Sieve test
            case "$OP" in
                contains)     MATCHER=':contains'     ; NEGATE='' ;;
                not_contains) MATCHER=':contains'     ; NEGATE='not ' ;;
                equals)       MATCHER=':is'           ; NEGATE='' ;;
                starts_with)  MATCHER=':matches'      ; NEGATE='' ; VAL_ESC="${VAL_ESC}*" ;;
                ends_with)    MATCHER=':matches'      ; NEGATE='' ; VAL_ESC="*${VAL_ESC}" ;;
                regex)        MATCHER=':regex'        ; NEGATE='' ;;
                *)            MATCHER=':contains'     ; NEGATE='' ;;
            esac

            # Field name (header için)
            case "$FIELD" in
                from)         FIELD_NAME='"From"' ;;
                to)           FIELD_NAME='"To"' ;;
                subject)      FIELD_NAME='"Subject"' ;;
                cc)           FIELD_NAME='"Cc"' ;;
                bcc)          FIELD_NAME='"Bcc"' ;;
                reply-to)     FIELD_NAME='"Reply-To"' ;;
                body)         FIELD_NAME='' ;;  # body test field almaz
                any_header)   FIELD_NAME='["From","To","Subject","Cc"]' ;;
                # X-Spam-Status, X-Spam-Flag, X-Spam, X-Spamd-Result vb. — raw header isimleri.
                # "x-" veya büyük harf ile başlayan field, custom header olarak quoted-name döner.
                x-*|X-*)      FIELD_NAME="\"${FIELD}\"" ;;
                *)            FIELD_NAME="\"${FIELD}\"" ;;  # Bilinmeyen field = literal header
            esac

            # Test string oluştur
            if [[ "$FIELD" == "body" ]]; then
                TEST="${NEGATE}${COND_PREFIX} ${MATCHER} \"${VAL_ESC}\""
            else
                TEST="${NEGATE}${COND_PREFIX} ${MATCHER} ${FIELD_NAME} \"${VAL_ESC}\""
            fi

            # Action
            case "$ACT" in
                discard)
                    printf 'if %s { discard; stop; }\n' "$TEST" ;;
                fail|reject)
                    printf 'if %s { reject "Filtered."; stop; }\n' "$TEST" ;;
                save_to_folder|fileinto)
                    printf 'if %s { fileinto :create "%s"; stop; }\n' "$TEST" "$ACT_VAL_ESC" ;;
                redirect|forward)
                    if [[ "$STOP" == "true" || "$STOP" == "1" ]]; then
                        printf 'if %s { redirect "%s"; stop; }\n' "$TEST" "$ACT_VAL_ESC"
                    else
                        printf 'if %s { redirect :copy "%s"; }\n' "$TEST" "$ACT_VAL_ESC"
                    fi ;;
                *)
                    printf '# unknown action: %s\n' "$ACT" ;;
            esac
        done
        printf '\n'

        # Forward (mailbox-wide — tüm gelen mailleri yönlendir)
        printf '# --- Forwarders ---\n'
        echo "$INPUT" | jq -c '.rules[]? | select(.type=="forward")' | while read -r row; do
            [[ -n "$row" ]] || continue
            KEEP_COPY="$(echo "$row" | jq -r '.keep_local_copy // true')"
            echo "$row" | jq -r '.destinations[]?' | while read -r dest; do
                [[ -n "$dest" ]] || continue
                dest_esc=$(sieve_escape "$dest")
                if [[ "$KEEP_COPY" == "true" || "$KEEP_COPY" == "1" ]]; then
                    printf 'redirect :copy "%s";\n' "$dest_esc"
                else
                    printf 'redirect "%s";\n' "$dest_esc"
                fi
            done
        done
        printf '\n'

        # Vacation
        # Sieve quoted-string newline desteklemez (RFC 5228) — multi-line body
        # için "text:" heredoc syntax kullanılır. Tek satır body'de "..." OK.
        # NOT: jq -r --raw-output ile gerçek newline karakterleri çıkar.
        VAC_MSG="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="vacation") | .message][0] // ""')"
        if [[ -n "$VAC_MSG" && "$VAC_MSG" != "null" ]]; then
            VAC_SUBJ="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="vacation") | .subject][0] // "Otomatik yanit"')"
            VAC_DAYS="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="vacation") | .days][0] // 7')"
            VAC_FROM="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="vacation") | .from][0] // ""')"
            VAC_SUBJ_ESC=$(sieve_escape "$VAC_SUBJ")
            printf '# --- Vacation/Autoresponder ---\n'

            # :from header
            FROM_PART=""
            if [[ -n "$VAC_FROM" && "$VAC_FROM" != "null" ]]; then
                VAC_FROM_ESC=$(sieve_escape "$VAC_FROM")
                FROM_PART=" :from \"${VAC_FROM_ESC}\""
            fi

            # Multi-line body kontrolü — newline varsa text: heredoc syntax
            if [[ "$VAC_MSG" == *$'\n'* ]]; then
                # text: heredoc — '.' tek başına satırda body'yi sonlandırır.
                # Eğer body içinde tek '.' satır varsa Sieve uyarınca dot-stuffing
                # gerekir: '..' yap (RFC 5228 §2.4.2.1).
                ESCAPED_BODY=$(printf '%s' "$VAC_MSG" | sed 's/^\./../')
                printf 'vacation :days %s :subject "%s"%s text:\n%s\n.\n;\n\n' \
                    "$VAC_DAYS" "$VAC_SUBJ_ESC" "$FROM_PART" "$ESCAPED_BODY"
            else
                # Tek satır — quoted string
                VAC_MSG_ESC=$(sieve_escape "$VAC_MSG")
                printf 'vacation :days %s :subject "%s"%s "%s";\n\n' \
                    "$VAC_DAYS" "$VAC_SUBJ_ESC" "$FROM_PART" "$VAC_MSG_ESC"
            fi
        fi

        # BoxTrapper: whitelist dışındakileri Quarantine'e
        if [[ "$BOXTRAPPER" == "true" ]]; then
            printf '# --- BoxTrapper (whitelist outside -> Quarantine) ---\n'
            WL_LIST="$(echo "$INPUT" | jq -r '[.rules[]? | select(.type=="whitelist") | .sender] | map("\"" + . + "\"") | join(", ")')"
            if [[ -n "$WL_LIST" && "$WL_LIST" != "null" ]]; then
                printf 'if not envelope :is "from" [%s] {\n' "$WL_LIST"
                printf '    fileinto :create "Quarantine";\n'
                printf '    stop;\n'
                printf '}\n'
            else
                printf '# No whitelist defined — boxtrapper disabled.\n'
            fi
        fi
    } > "$SIEVE_TMP"
fi

# Aktive et
install -o vmail -g vmail -m 0600 "$SIEVE_TMP" "$SIEVE_FILE"
BYTES="$(wc -c < "$SIEVE_FILE" | tr -d ' ')"

# doveadm sieve put + compile
# &>/dev/null kritik — doveadm stdout'a "Sieve script uploaded" gibi mesajlar
# yazar, JSON return value'yu kirletir → PHP json_decode fail eder.
if command -v doveadm >/dev/null 2>&1; then
    doveadm sieve put -u "$EMAIL" "managesieve" < "$SIEVE_FILE" &>/dev/null || true
    doveadm sieve activate -u "$EMAIL" "managesieve" &>/dev/null || true
fi

onx_audit "onx-sieve" "wrote ${SIEVE_FILE} (${BYTES} bytes) for ${EMAIL}"

cat <<EOF
{"ok":true,"script_path":"${SIEVE_FILE}","bytes":${BYTES},"message":"Sieve script kaydedildi ve aktif edildi"}
EOF
