package chat.vojo.app;

import android.content.Context;
import android.content.SharedPreferences;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * Cross-source LRU dedup for rendered push event_ids.
 *
 * Both the FCM service (after a successful nm.notify) and the polling Worker
 * write into the same bounded SharedPreferences-backed set. The Worker reads
 * it to skip events FCM already delivered — which fixes the regression where
 * a user who dismissed an FCM notification before polling fired would see
 * the same event resurface up to 15 minutes later via the polling fallback.
 *
 * The native `eventId.hashCode()` notification-id slot is still the primary
 * dedup for *concurrent* render (Android NotificationManager replace), but
 * that only collapses surfaces while both notifications are still visible;
 * once the user dismisses, the slot is empty and the second render would
 * post fresh. This shared set covers that gap.
 *
 * Synchronisation: SharedPreferences read-modify-write is not atomic across
 * threads/processes, and FCM service runs on a Firebase-managed background
 * thread while the Worker runs on WorkManager's executor. We serialise all
 * mutations through a static lock. Critical sections are short (string split
 * + LinkedHashSet trim + putString) — no Binder calls.
 */
final class NotificationDedup {

    // Capacity is intentionally larger than VojoPollWorker's worst-case per-run
    // event count (MAX_PAGES_PER_RUN × PAGE_LIMIT = 250). If a single fire
    // marks 250 events and the cap were 200, the 50 oldest of those would
    // already be evicted by the time we finish writing — so a sibling poll
    // resuming the same window would re-render them. 500 gives 2× headroom
    // while staying ~12 KB in SharedPreferences (negligible).
    private static final int MAX_TRACKED = 500;
    private static final Object lock = new Object();

    private NotificationDedup() {}

    /** Returns true iff the given event_id has been notified in a recent cycle. */
    static boolean wasNotified(Context ctx, String eventId) {
        if (eventId == null || eventId.isEmpty()) return false;
        synchronized (lock) {
            return readSet(ctx).contains(eventId);
        }
    }

    /** Append the event_id to the LRU set, trimming the oldest when full. */
    static void markNotified(Context ctx, String eventId) {
        if (eventId == null || eventId.isEmpty()) return;
        synchronized (lock) {
            Set<String> set = readSet(ctx);
            // LinkedHashSet preserves insertion order — re-adding moves to tail
            // only if we remove-then-add. The Set#add no-op on a present entry
            // does NOT refresh position, but the simple "drop oldest" trim
            // below is adequate for our scale and matches the Worker's
            // existing semantics. Skip the disk write entirely when add()
            // returned false — the event was already in the set, persistence
            // would just churn SharedPreferences for no state change.
            if (!set.add(eventId)) return;
            if (set.size() > MAX_TRACKED) {
                Iterator<String> it = set.iterator();
                int drop = set.size() - MAX_TRACKED;
                while (it.hasNext() && drop > 0) {
                    it.next();
                    it.remove();
                    drop -= 1;
                }
            }
            writeSet(ctx, set);
        }
    }

    /** Caller must hold {@link #lock}. */
    private static Set<String> readSet(Context ctx) {
        SharedPreferences prefs = ctx.getSharedPreferences(
            VojoPollWorker.PREFS, Context.MODE_PRIVATE);
        String raw = prefs.getString(VojoPollWorker.KEY_NOTIFIED_IDS, "");
        Set<String> out = new LinkedHashSet<>();
        if (raw.isEmpty()) return out;
        for (String id : raw.split(",")) {
            if (!id.isEmpty()) out.add(id);
        }
        return out;
    }

    /** Caller must hold {@link #lock}. */
    private static void writeSet(Context ctx, Set<String> set) {
        SharedPreferences prefs = ctx.getSharedPreferences(
            VojoPollWorker.PREFS, Context.MODE_PRIVATE);
        StringBuilder sb = new StringBuilder(set.size() * 25);
        boolean first = true;
        for (String id : set) {
            if (!first) sb.append(',');
            sb.append(id);
            first = false;
        }
        prefs.edit().putString(VojoPollWorker.KEY_NOTIFIED_IDS, sb.toString()).apply();
    }
}
