#!/bin/bash
# mailsync — full mail update pipeline for aerc
#   1. mbsync: pull all mail (config: ~/.config/isync/mksyncrc)
#   2. notmuch new: index every account's database
#   3. abook: rebuild contacts from every unique sender (phil + proton)
# Triggered by: aerc keybind `u`, systemd user timers (08:00/12:00/17:00 + login+2min)

set -u

# ---------- configuration ----------
SYNC_CONF="$HOME/.config/isync/mksyncrc"
NM_CONF_DIR="$HOME/.config/notmuch"
ABOOK_RC="$HOME/.config/abook/abookrc"
ABOOK_DB="$HOME/.local/share/abook/addressbook"
LOG_DIR="$HOME/.local/share/aerc/logs"
LOG="$LOG_DIR/mailsync.log"
LOCK="$HOME/.local/share/aerc/mailsync.lock"
LOG_KEEP=7

CHANNELS="liphlink-phil liphlink-spam proton"   # mbsync channels
INDEX_ACCOUNTS="phil proton spam"               # notmuch databases to index
CONTACT_ACCOUNTS="phil proton"                  # accounts harvested for abook

HARVEST_EXCLUDE='and not folder:Junk and not folder:Spam and not folder:Trash and not tag:deleted'
PHIL_QUERY="(folder:Inbox or folder:INBOX or folder:Sent or folder:Archive or folder:Personal or folder:Priority1 or folder:Priority2 or folder:Work) $HARVEST_EXCLUDE"
PROTON_QUERY="(folder:INBOX or folder:Sent or folder:Archive or folder:Starred) and not folder:Spam and not folder:Trash and not tag:deleted"

# own addresses are never added as contacts
# own addresses are never added as contacts; MAILER-DAEMON bounces are noise
OWN_ADDRESSES='phil@liphlink.xyz|pw@liphlink.xyz|p\.waibel@liphlink.xyz|philipp\.waibel@liphlink.xyz|spam@liphlink.xyz|blue@liphlink.xyz|red@liphlink.xyz|liiph@protonmail.com|liiph@proton.me|liiph@pm.me|ph\.waibel@proton.me|ph\.waibel@pm.me|liph@liphlink.xyz|mailer-daemon@'
# -----------------------------------

exec 9>"$LOCK"
if ! flock -n 9; then
    echo "mailsync: already running, skipping."
    exit 0
fi

mkdir -p "$LOG_DIR" "$(dirname "$ABOOK_RC")" "$(dirname "$ABOOK_DB")"

# ---------- log rotation (keep last $LOG_KEEP previous runs) ----------
rm -f "$LOG_DIR/mailsync.log.$LOG_KEEP"
i=$((LOG_KEEP - 1))
while [ "$i" -ge 1 ]; do
    [ -f "$LOG_DIR/mailsync.log.$i" ] && mv "$LOG_DIR/mailsync.log.$i" "$LOG_DIR/mailsync.log.$((i + 1))"
    i=$((i - 1))
done
[ -f "$LOG" ] && mv "$LOG" "$LOG_DIR/mailsync.log.1"

exec > >(tee "$LOG") 2>&1
echo "=== mailsync started: $(date '+%Y-%m-%d %H:%M:%S') ==="

# ---------- 1. mbsync: pull mail ----------
echo
echo "--- mbsync ---"
sync_ok=0
sync_fail=0
for ch in $CHANNELS; do
    if [ "$ch" = "proton" ] && ! pgrep -f protonmail-bridge >/dev/null 2>&1; then
        echo "  WARN: Proton Bridge not running — skipping proton"
        continue
    fi
    printf '  syncing %-16s ... ' "$ch"
    if mbsync -c "$SYNC_CONF" "$ch" >/dev/null 2>&1; then
        echo "OK"
        sync_ok=$((sync_ok + 1))
    else
        echo "FAILED"
        sync_fail=$((sync_fail + 1))
    fi
done
echo "  (ok: $sync_ok, failed: $sync_fail)"

# ---------- 2. notmuch: index ----------
echo
echo "--- notmuch new ---"
index_ok=0
for acct in $INDEX_ACCOUNTS; do
    printf '  indexing %-8s ... ' "$acct"
    nm_out=$(NOTMUCH_CONFIG="$NM_CONF_DIR/$acct/config" notmuch new 2>&1)
    if [ $? -eq 0 ]; then
        echo "OK"
        echo "$nm_out" | grep -v '^$' | sed 's/^/    /'
        index_ok=$((index_ok + 1))
    else
        echo "FAILED"
        echo "$nm_out" | sed 's/^/    /'
    fi
done

# ---------- 3. abook: rebuild contacts ----------
echo
echo "--- abook contacts ---"

# Initialize abook only if absent; never clobber existing data.
if [ ! -f "$ABOOK_RC" ]; then
    printf '# abook config (managed by mailsync)\n[options]\nautosave=true\n' > "$ABOOK_RC"
    echo "  created $ABOOK_RC"
fi
if [ ! -f "$ABOOK_DB" ]; then
    printf '# abook addressbook file\n\n[format]\nprogram=abook\nversion=0.6.2\n' > "$ABOOK_DB"
    echo "  created $ABOOK_DB"
fi

# harvest: emit "Name <email>" (or bare "<email>") lines from an account's db
harvest() {
    NOTMUCH_CONFIG="$NM_CONF_DIR/$1/config" notmuch address --output=sender "$2" 2>/dev/null
}

# "Name <email>" lines -> tab pairs "email\tname"; dedupe by email, keep longest name
to_pairs() {
    grep -viE "$OWN_ADDRESSES" | awk -F'[<>]' '
    {
        if ($2 != "") { email = $2; name = $1 }
        else          { email = $0; name = "" }
        gsub(/^[ \t]+|[ \t]+$/, "", email)
        gsub(/^[ \t]+|[ \t]+$/, "", name)
        gsub(/^"|"$/, "", name)
        if (email !~ /@/) next
        if (!(email in seen) || length(name) > length(seen[email])) seen[email] = name
        if (!(email in order)) order[email] = ++n
    }
    END { for (e in order) printf "%s\t%s\n", e, seen[e] }
    '
}

harvest_tmp="$(mktemp /tmp/mailsync-harvest.XXXXXX)"

for acct in $CONTACT_ACCOUNTS; do
    q="$PHIL_QUERY"; [ "$acct" = "proton" ] && q="$PROTON_QUERY"
    printf '  harvesting %-8s ... ' "$acct"
    n=$(harvest "$acct" "$q" | wc -l)
    echo "$n sender lines"
    harvest "$acct" "$q" | to_pairs >> "$harvest_tmp"
done

# existing entries (email\tname) parsed straight from the current datafile
existing_tmp="$(mktemp /tmp/mailsync-existing.XXXXXX)"
awk '/^name=/{n=substr($0,6)} /^email=/{e=substr($0,7); if (e != "") printf "%s\t%s\n", e, n}' \
    "$ABOOK_DB" | sort > "$existing_tmp"

# union: existing entries win (manual edits preserved); add harvested ones not present
merge_tmp="$(mktemp /tmp/mailsync-merge.XXXXXX)"
awk -F'\t' '
    NR==FNR { if ($1 != "") existing[$1] = $2; next }
    { if (!($1 in existing)) new[$1] = $2; ++m; ord[m] = $1 }
    END {
        for (e in existing) printf "%s\t%s\n", existing[e], e
        for (k = 1; k <= m; k++) { e = ord[k]; if (e in new) printf "%s\t%s\n", new[e], e }
    }' "$existing_tmp" "$harvest_tmp" \
    | awk -F'\t' '!seen[$2]++' \
    | sort -t$'\t' -k1,1 > "$merge_tmp"

# write final native-format abook file atomically (only after validation passes)
final_tmp="$(mktemp /tmp/mailsync-abook.XXXXXX)"
{
    echo "# abook addressbook file"
    echo ""
    echo "[format]"
    echo "program=abook"
    echo "version=0.6.2"
    echo ""
    n=0
    while IFS=$'\t' read -r name email; do   # merge_tmp is "name\temail"
        [ -z "$email" ] && continue
        printf '[%d]\nname=%s\nemail=%s\n\n' "$n" "$name" "$email"
        n=$((n + 1))
    done < "$merge_tmp"
} > "$final_tmp"

count=$(awk '/^\[[0-9]+\]/{c++} END{print c+0}' "$final_tmp")
if abook --convert --informat abook --outformat mutt --infile "$final_tmp" --outfile /dev/null >/dev/null 2>&1; then
    [ -f "$ABOOK_DB.pre-mailsync" ] || cp "$ABOOK_DB" "$ABOOK_DB.pre-mailsync" 2>/dev/null
    mv "$final_tmp" "$ABOOK_DB"
    echo "  abook updated: $count contacts ($(awk -F'\t' '{c++} END{print c}' "$existing_tmp") kept, $(awk -F'\t' '{c++} END{print c}' "$merge_tmp") total)"
else
    echo "  ERROR: generated abook file failed validation — keeping existing datafile"
    rm -f "$final_tmp"
fi

rm -f "$harvest_tmp" "$existing_tmp" "$merge_tmp"

# ---------- summary ----------
echo
echo "=== mailsync finished: $(date '+%Y-%m-%d %H:%M:%S') (sync ok=$sync_ok fail=$sync_fail, index ok=$index_ok) ==="

# keep the aerc terminal open so output can be read
if [ -t 1 ]; then
    printf 'Done. Press any key to close...'
    read -n 1 -s -r
    echo
fi