-- Migration: Finalize all notification enqueue functions
-- Supersedes the function definitions in 20260321_extend_scheduled_notifications.sql.
-- Schema changes (ALTER TABLE, indexes, constraints) remain in that file.
--
-- Bugs fixed in this migration:
--   1. duty_schedules status 'active' was not a valid enum value → corrected to IN ('arrival', 'late')
--   2. end_hourly had no time cap → now only enqueues for shifts that ended within 24 hours
--   3. pass_slip_expiry used a narrow ±90s window → now uses the full 45–60 min range
--      so the function doesn't need to be called at an exact second to work

-- ============================================================================
-- ENQUEUE FUNCTIONS
-- ============================================================================

-- ----------------------------------------------------------------------------
-- enqueue_due_shift_notifications()
--   start_15  — 15 min before shift start (narrow ±90s window, send once)
--   end       — at exact shift end time   (narrow ±90s window, send once)
--   end_hourly — every hour after shift end until user checks out
--                (only for shifts that ended within the last 24 hours)
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_due_shift_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
  v_hours_since_end int;
  v_latest_hourly timestamptz;
BEGIN
  -- 15-minute-before reminders
  FOR rec IN
    SELECT id AS schedule_id, user_id, start_time AS start_at
    FROM public.duty_schedules
    WHERE start_time BETWEEN now() + interval '15 minutes' - interval '90 seconds'
                         AND now() + interval '15 minutes' + interval '90 seconds'
      AND status IN ('arrival', 'late')
  LOOP
    -- Skip if user already checked in
    IF EXISTS (
      SELECT 1 FROM public.attendance_logs al
      WHERE al.duty_schedule_id = rec.schedule_id AND al.check_in_at IS NOT NULL
    ) THEN
      CONTINUE;
    END IF;

    INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
    VALUES (rec.schedule_id, rec.user_id, 'start_15', rec.start_at)
    ON CONFLICT DO NOTHING;
  END LOOP;

  -- End-of-shift reminders
  FOR rec IN
    SELECT id AS schedule_id, user_id, end_time AS end_at
    FROM public.duty_schedules
    WHERE end_time BETWEEN now() - interval '90 seconds' AND now() + interval '90 seconds'
      AND status IN ('arrival', 'late')
  LOOP
    INSERT INTO public.scheduled_notifications (schedule_id, user_id, notify_type, scheduled_for)
    VALUES (rec.schedule_id, rec.user_id, 'end', rec.end_at)
    ON CONFLICT DO NOTHING;
  END LOOP;

  -- Hourly checkout reminders (shifts ended within the last 24 hours only)
  FOR rec IN
    SELECT ds.id AS schedule_id, ds.user_id, ds.end_time
    FROM public.duty_schedules ds
    JOIN public.attendance_logs al ON al.duty_schedule_id = ds.id AND al.user_id = ds.user_id
    WHERE ds.end_time < now()
      AND ds.end_time >= now() - interval '24 hours'
      AND ds.status IN ('arrival', 'late')
      AND al.check_in_at IS NOT NULL
      AND al.check_out_at IS NULL
      AND ds.shift_type != 'overtime'
  LOOP
    v_hours_since_end := GREATEST(1, EXTRACT(EPOCH FROM (now() - rec.end_time))::int / 3600);

    SELECT MAX(scheduled_for) INTO v_latest_hourly
    FROM public.scheduled_notifications
    WHERE schedule_id = rec.schedule_id
      AND user_id = rec.user_id
      AND notify_type = 'end_hourly';

    -- Only enqueue if no hourly reminder sent yet, or the last one was >55 min ago
    IF v_latest_hourly IS NULL OR v_latest_hourly < now() - interval '55 minutes' THEN
      INSERT INTO public.scheduled_notifications
        (schedule_id, user_id, notify_type, scheduled_for, epoch)
      VALUES
        (rec.schedule_id, rec.user_id, 'end_hourly', now(), v_hours_since_end)
      ON CONFLICT DO NOTHING;
    END IF;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_overtime_idle_notifications()
--   15 minutes into overtime with no active task or ISR
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_overtime_idle_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
BEGIN
  FOR rec IN
    SELECT ds.id AS schedule_id, ds.user_id
    FROM public.duty_schedules ds
    JOIN public.attendance_logs al ON al.duty_schedule_id = ds.id AND al.user_id = ds.user_id
    WHERE ds.shift_type = 'overtime'
      AND ds.status IN ('arrival', 'late')
      AND al.check_in_at IS NOT NULL
      AND al.check_out_at IS NULL
      AND al.check_in_at <= now() - interval '15 minutes'
      AND NOT EXISTS (
        SELECT 1 FROM public.task_assignments ta
        JOIN public.tasks t ON t.id = ta.task_id
        WHERE ta.user_id = ds.user_id AND t.status = 'in_progress'
      )
      AND NOT EXISTS (
        SELECT 1 FROM public.it_service_request_assignments isra
        JOIN public.it_service_requests isr ON isr.id = isra.request_id
        WHERE isra.user_id = ds.user_id AND isr.status IN ('in_progress', 'in_progress_dry_run')
      )
  LOOP
    INSERT INTO public.scheduled_notifications
      (schedule_id, user_id, notify_type, scheduled_for)
    VALUES
      (rec.schedule_id, rec.user_id, 'overtime_idle_15', now())
    ON CONFLICT DO NOTHING;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_overtime_checkout_notifications()
--   30 min after last task completion during overtime, no new task started,
--   and at least 1 hour since overtime check-in
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_overtime_checkout_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
  v_last_completed timestamptz;
BEGIN
  FOR rec IN
    SELECT ds.id AS schedule_id, ds.user_id, al.check_in_at
    FROM public.duty_schedules ds
    JOIN public.attendance_logs al ON al.duty_schedule_id = ds.id AND al.user_id = ds.user_id
    WHERE ds.shift_type = 'overtime'
      AND ds.status IN ('arrival', 'late')
      AND al.check_in_at IS NOT NULL
      AND al.check_out_at IS NULL
      AND al.check_in_at <= now() - interval '1 hour'
      AND NOT EXISTS (
        SELECT 1 FROM public.task_assignments ta
        JOIN public.tasks t ON t.id = ta.task_id
        WHERE ta.user_id = ds.user_id AND t.status = 'in_progress'
      )
  LOOP
    SELECT MAX(t.completed_at) INTO v_last_completed
    FROM public.task_assignments ta
    JOIN public.tasks t ON t.id = ta.task_id
    WHERE ta.user_id = rec.user_id
      AND t.status IN ('completed', 'closed')
      AND t.completed_at IS NOT NULL
      AND t.completed_at >= rec.check_in_at;

    IF v_last_completed IS NOT NULL AND v_last_completed <= now() - interval '30 minutes' THEN
      INSERT INTO public.scheduled_notifications
        (schedule_id, user_id, notify_type, scheduled_for)
      VALUES
        (rec.schedule_id, rec.user_id, 'overtime_checkout_30', now())
      ON CONFLICT DO NOTHING;
    END IF;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_isr_event_notifications()
--   1 hour before IT service request event_date
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_isr_event_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
BEGIN
  FOR rec IN
    SELECT isr.id AS request_id, isr.event_date, isra.user_id
    FROM public.it_service_requests isr
    JOIN public.it_service_request_assignments isra ON isra.request_id = isr.id
    WHERE isr.status IN ('scheduled', 'in_progress_dry_run')
      AND isr.event_date IS NOT NULL
      AND isr.event_date BETWEEN now() + interval '60 minutes' - interval '90 seconds'
                              AND now() + interval '60 minutes' + interval '90 seconds'
  LOOP
    INSERT INTO public.scheduled_notifications
      (it_service_request_id, user_id, notify_type, scheduled_for)
    VALUES
      (rec.request_id, rec.user_id, 'isr_event_60', rec.event_date - interval '1 hour')
    ON CONFLICT DO NOTHING;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_isr_evidence_notifications()
--   Daily reminder for assigned users missing evidence/action upload,
--   only after user has checked in today
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_isr_evidence_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
  v_today_doy int := EXTRACT(DOY FROM now())::int;
BEGIN
  FOR rec IN
    SELECT isr.id AS request_id, isra.user_id
    FROM public.it_service_requests isr
    JOIN public.it_service_request_assignments isra ON isra.request_id = isr.id
    WHERE isr.status IN ('completed', 'in_progress')
      AND (
        NOT EXISTS (
          SELECT 1 FROM public.it_service_request_evidence e
          WHERE e.request_id = isr.id AND e.user_id = isra.user_id
        )
        OR
        NOT EXISTS (
          SELECT 1 FROM public.it_service_request_actions a
          WHERE a.request_id = isr.id AND a.user_id = isra.user_id
            AND a.action_taken IS NOT NULL AND TRIM(a.action_taken) != ''
        )
      )
      AND EXISTS (
        SELECT 1 FROM public.attendance_logs al
        WHERE al.user_id = isra.user_id
          AND al.check_in_at::date = now()::date
      )
  LOOP
    INSERT INTO public.scheduled_notifications
      (it_service_request_id, user_id, notify_type, scheduled_for, epoch)
    VALUES
      (rec.request_id, rec.user_id, 'isr_evidence_daily', now(), v_today_doy)
    ON CONFLICT DO NOTHING;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_paused_task_notifications()
--   Daily reminder for paused tasks, only after user has checked in today
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_paused_task_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
  v_today_doy int := EXTRACT(DOY FROM now())::int;
BEGIN
  FOR rec IN
    SELECT t.id AS task_id, ta.user_id
    FROM public.tasks t
    JOIN public.task_assignments ta ON ta.task_id = t.id
    WHERE t.status = 'in_progress'
      AND EXISTS (
        SELECT 1 FROM public.task_activity_logs tal
        WHERE tal.task_id = t.id
          AND tal.action_type = 'paused'
          AND NOT EXISTS (
            SELECT 1 FROM public.task_activity_logs tal2
            WHERE tal2.task_id = t.id
              AND tal2.created_at > tal.created_at
              AND tal2.action_type IN ('resumed', 'completed', 'cancelled')
          )
      )
      AND EXISTS (
        SELECT 1 FROM public.attendance_logs al
        WHERE al.user_id = ta.user_id
          AND al.check_in_at::date = now()::date
      )
  LOOP
    INSERT INTO public.scheduled_notifications
      (task_id, user_id, notify_type, scheduled_for, epoch)
    VALUES
      (rec.task_id, rec.user_id, 'task_paused_daily', now(), v_today_doy)
    ON CONFLICT DO NOTHING;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_backlog_notifications()
--   15 min before shift end, for users with pending non-paused tasks
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_backlog_notifications()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  rec RECORD;
BEGIN
  FOR rec IN
    SELECT ds.id AS schedule_id, ds.user_id, ds.end_time
    FROM public.duty_schedules ds
    WHERE ds.end_time BETWEEN now() + interval '15 minutes' - interval '90 seconds'
                          AND now() + interval '15 minutes' + interval '90 seconds'
      AND ds.status IN ('arrival', 'late')
      AND EXISTS (
        SELECT 1
        FROM public.task_assignments ta
        JOIN public.tasks t ON t.id = ta.task_id
        WHERE ta.user_id = ds.user_id
          AND t.status IN ('queued', 'in_progress')
          AND NOT EXISTS (
            SELECT 1 FROM public.task_activity_logs tal
            WHERE tal.task_id = t.id
              AND tal.action_type = 'paused'
              AND NOT EXISTS (
                SELECT 1 FROM public.task_activity_logs tal2
                WHERE tal2.task_id = t.id
                  AND tal2.created_at > tal.created_at
                  AND tal2.action_type IN ('resumed', 'completed', 'cancelled')
              )
          )
      )
  LOOP
    INSERT INTO public.scheduled_notifications
      (schedule_id, user_id, notify_type, scheduled_for)
    VALUES
      (rec.schedule_id, rec.user_id, 'backlog_15', rec.end_time - interval '15 minutes')
    ON CONFLICT DO NOTHING;
  END LOOP;
END;
$$;

-- ----------------------------------------------------------------------------
-- enqueue_pass_slip_expiry_notifications()
--   Notifies when an active pass slip is in its last 15 minutes (45–60 min old).
--   Uses a 15-minute window instead of a narrow ±90s window so the function
--   doesn't need to be called at an exact second. ON CONFLICT DO NOTHING
--   ensures only one notification is inserted per pass slip.
-- ----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.enqueue_pass_slip_expiry_notifications()
RETURNS void LANGUAGE plpgsql AS $$
BEGIN
  INSERT INTO public.scheduled_notifications (pass_slip_id, user_id, notify_type, scheduled_for)
  SELECT ps.id, ps.user_id, 'pass_slip_expiry_15', now()
  FROM public.pass_slips ps
  WHERE ps.status = 'approved'
    AND ps.slip_end IS NULL
    AND ps.slip_start IS NOT NULL
    AND ps.slip_start + interval '45 minutes' <= now()
    AND ps.slip_start + interval '60 minutes' > now()
  ON CONFLICT DO NOTHING;
END;
$$;

-- ============================================================================
-- MASTER DISPATCHER
-- ============================================================================
-- NOTE: This file has an underscore at position 9 of its filename, which makes
-- it sort AFTER all 20260322NNNNNN_* files (underscore ASCII 95 > digits 48-57).
-- It therefore runs LAST among all 20260322 migrations and must include every
-- enqueue function defined by those earlier migrations.
CREATE OR REPLACE FUNCTION public.enqueue_all_notifications()
RETURNS void LANGUAGE plpgsql AS $$
BEGIN
  PERFORM public.enqueue_due_shift_notifications();
  PERFORM public.enqueue_overtime_idle_notifications();
  PERFORM public.enqueue_overtime_checkout_notifications();
  PERFORM public.enqueue_isr_event_notifications();
  PERFORM public.enqueue_isr_evidence_notifications();
  PERFORM public.enqueue_paused_task_notifications();
  PERFORM public.enqueue_backlog_notifications();
  PERFORM public.enqueue_pass_slip_expiry_notifications();
  PERFORM public.enqueue_pass_slip_expired_notifications();      -- added in 20260322150000
  PERFORM public.enqueue_announcement_banner_notifications();    -- added in 20260322210000
END;
$$;

-- ============================================================================
-- PG_NET DISPATCHER (called by pg_cron)
-- ============================================================================
-- The edge function (process_scheduled_notifications) calls enqueue_all_notifications()
-- via RPC internally, so a single HTTP call handles both enqueue and process.
--
-- ONE-TIME VAULT SETUP (run once, not part of migration):
--   SELECT vault.create_secret('https://YOUR_PROJECT.supabase.co', 'supabase_url');
--   SELECT vault.create_secret('YOUR_SERVICE_ROLE_KEY', 'service_role_key');
--
-- ALTERNATIVE: Ubuntu server crontab (no pg_cron needed):
--   */1 * * * * [REDACTED] \
--     -H "Content-Type: application/json" -d '{}'

CREATE OR REPLACE FUNCTION public.process_notification_queue()
RETURNS void LANGUAGE plpgsql AS $$
DECLARE
  v_base_url text;
  v_service_key text;
BEGIN
  BEGIN
    SELECT decrypted_secret INTO v_service_key
    FROM vault.decrypted_secrets WHERE name = 'service_role_key' LIMIT 1;

    SELECT decrypted_secret INTO v_base_url
    FROM vault.decrypted_secrets WHERE name = 'supabase_url' LIMIT 1;
  EXCEPTION WHEN others THEN
    NULL;
  END;

  IF v_base_url IS NULL THEN
    v_base_url := current_setting('app.settings.supabase_url', true);
  END IF;
  IF v_service_key IS NULL THEN
    v_service_key := current_setting('app.settings.service_role_key', true);
  END IF;

  IF v_base_url IS NULL OR v_service_key IS NULL THEN
    RAISE WARNING 'process_notification_queue: vault secrets not configured. Use external cron instead.';
    RETURN;
  END IF;

  PERFORM net.http_post(
    url := v_base_url || '/functions/v1/process_scheduled_notifications',
    headers := jsonb_build_object(
      'Content-Type', 'application/json',
      'Authorization', 'Bearer ' || v_service_key
    ),
    body := '{}'::jsonb
  );
END;
$$;

-- ============================================================================
-- PG_CRON JOBS (requires pg_cron extension enabled in Supabase Dashboard)
-- If pg_cron is not available, use the Ubuntu crontab approach above instead.
-- ============================================================================
DO $$
BEGIN
  -- Clean up any old job names
  BEGIN PERFORM cron.unschedule('shift_reminders_every_min'); EXCEPTION WHEN others THEN NULL; END;
  BEGIN PERFORM cron.unschedule('notification_reminders_every_min'); EXCEPTION WHEN others THEN NULL; END;
  BEGIN PERFORM cron.unschedule('notification_enqueue_every_min'); EXCEPTION WHEN others THEN NULL; END;

  -- Single job: triggers edge function which handles enqueue + process internally
  PERFORM cron.schedule(
    'notification_process_every_min',
    '*/1 * * * *',
    'SELECT public.process_notification_queue();'
  );

  -- Daily cleanup of old processed rows
  PERFORM cron.schedule(
    'cleanup_old_notifications',
    '0 3 * * *',
    'DELETE FROM scheduled_notifications WHERE processed = true AND processed_at < now() - interval ''7 days''; DELETE FROM notification_pushes WHERE pushed_at < now() - interval ''7 days'';'
  );

EXCEPTION WHEN others THEN
  RAISE NOTICE 'pg_cron not enabled (%). Use the Ubuntu crontab approach instead.', SQLERRM;
END $$;
