-- Fix: eliminate double push notifications for announcement banners.
--
-- Root causes addressed:
--   1. scheduled_for = now() made rows immediately eligible. Multiple consecutive
--      epoch rows (e.g. E and E+1) all had scheduled_for in the past, so all were
--      eligible simultaneously. ON CONFLICT DO NOTHING kept the old rows intact
--      (didn't update scheduled_for to the future), so every processBatch run
--      found 2+ eligible rows → 2+ FCM sends.
--      Fix A (SQL): scheduled_for = start of the NEXT epoch window (future).
--      Fix B (SQL): one-time DELETE of all pre-existing stale rows so the queue
--                   starts clean. The edge function deduplication (Fix C) also
--                   guards against any future accumulation.
--      Fix C (TS):  processBatch deduplicates announcement_banner rows by
--                   (announcement_id, user_id), keeping only the highest-epoch
--                   row and marking stale ones processed without sending FCM.
--
--   2. epoch < v_epoch (too aggressive) deleted epoch E's row at the start of
--      epoch E+1 — the row that was due RIGHT NOW — stopping all notifications.
--      Fix: epoch < v_epoch - 1 keeps epoch E intact for processing.

-- ============================================================================
-- One-time cleanup: mark all pre-existing stale announcement_banner rows as
-- processed so they are no longer eligible. These were inserted with the old
-- scheduled_for = now() logic and have scheduled_for in the past, causing them
-- to appear in every processBatch query and generate duplicate pushes.
-- ============================================================================
DELETE FROM public.scheduled_notifications
WHERE notify_type = 'announcement_banner'
  AND processed   = false;

CREATE OR REPLACE FUNCTION public.enqueue_announcement_banner_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  ann      RECORD;
  usr      RECORD;
  v_epoch  int;
BEGIN
  FOR ann IN
    SELECT a.id, a.author_id, a.visible_roles, a.push_interval_minutes
    FROM   public.announcements a
    WHERE  a.banner_enabled           = true
      AND  a.push_interval_minutes   IS NOT NULL
      AND  a.is_template             = false
      AND  (a.banner_show_at IS NULL OR a.banner_show_at <= now())
      AND  (a.banner_hide_at IS NULL OR a.banner_hide_at  > now())
  LOOP
    -- One epoch per interval window; prevents duplicate pushes within the window.
    v_epoch := FLOOR(
      EXTRACT(EPOCH FROM now()) / (ann.push_interval_minutes * 60)
    )::int;

    -- Purge rows that are ≥2 epochs stale (unprocessed due to missed cycles).
    -- We keep epoch = v_epoch - 1 because that row's scheduled_for falls
    -- exactly at the current epoch boundary and is about to be processed.
    -- Deleting epoch < v_epoch would remove rows that are due RIGHT NOW
    -- (epoch E's row has scheduled_for = (E+1)*interval = now), causing
    -- all notifications to stop firing — which is the bug this replaces.
    DELETE FROM public.scheduled_notifications
    WHERE  announcement_id = ann.id
      AND  notify_type     = 'announcement_banner'
      AND  processed       = false
      AND  epoch           < v_epoch - 1;

    FOR usr IN
      SELECT p.id
      FROM   public.profiles p
      WHERE  p.role::text = ANY(ann.visible_roles)
        AND  p.id <> ann.author_id
    LOOP
      -- scheduled_for = start of the NEXT epoch window (not now()).
      -- This guarantees the row only becomes eligible AFTER the current
      -- epoch ends, so a concurrent or delayed edge-function instance can
      -- never see two different epochs' rows as simultaneously due.
      INSERT INTO public.scheduled_notifications
        (announcement_id, user_id, notify_type, scheduled_for, epoch)
      VALUES
        (ann.id, usr.id, 'announcement_banner',
         to_timestamp(((v_epoch + 1)::bigint * ann.push_interval_minutes * 60)),
         v_epoch)
      ON CONFLICT DO NOTHING;
    END LOOP;
  END LOOP;
END;
$$;
