-- Business rule: PM Duty always comes with ON_CALL (consecutive overnight
-- shift, same person, same calendar day).  Accepting a swap that involves a
-- PM schedule must also atomically transfer the companion on_call duty
-- schedule to the new PM holder so the pairing stays intact.
--
-- Handles BOTH swap directions:
--   A) target_shift_id is PM  → requester receives PM; transfer recipient's
--      companion ON_CALL to requester.
--   B) shift_id is PM         → recipient receives PM; transfer requester's
--      companion ON_CALL to recipient.
--
-- Example (direction A — Normal user initiates):
--   Before: User A owns Normal (08:00–17:00), User B owns PM (15:00–23:00)
--           and ON_CALL (23:00–07:00) on the same calendar day.
--   After accept: User A owns PM + ON_CALL, User B owns Normal.
--
-- Example (direction B — PM user initiates):
--   Before: User B owns PM + ON_CALL, User A owns Normal.
--   After accept: same result — User A owns PM + ON_CALL, User B owns Normal.
--
-- Supersedes 20260322180000_auto_reject_orphaned_swaps.sql.

CREATE OR REPLACE FUNCTION public.respond_shift_swap(p_swap_id uuid, p_action text)
RETURNS void LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
DECLARE
  v_swap              RECORD;
  v_target_type       text;
  v_target_date       date;
  v_requester_type    text;
  v_requester_date    date;
  v_companion_id      uuid;
BEGIN
  SELECT * INTO v_swap FROM public.swap_requests WHERE id = p_swap_id;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'swap request not found';
  END IF;

  IF p_action NOT IN ('accepted', 'rejected', 'admin_review') THEN
    RAISE EXCEPTION 'invalid action';
  END IF;

  -- Idempotency guard: already terminal → nothing to do.
  IF v_swap.status IN ('accepted', 'rejected') THEN
    RETURN;
  END IF;

  -- ── ACCEPTED ──────────────────────────────────────────────────────────────
  IF p_action = 'accepted' THEN

    -- Permission: recipient or admin/dispatcher
    IF NOT (
      v_swap.recipient_id = auth.uid()
      OR EXISTS (
        SELECT 1 FROM public.profiles p
        WHERE p.id = auth.uid()
          AND p.role IN ('admin', 'dispatcher')
      )
    ) THEN
      RAISE EXCEPTION 'permission denied';
    END IF;

    -- Ownership validation before swapping
    IF NOT EXISTS (
      SELECT 1 FROM public.duty_schedules
      WHERE id = v_swap.shift_id AND user_id = v_swap.requester_id
    ) THEN
      RAISE EXCEPTION 'requester shift ownership changed, cannot accept swap';
    END IF;

    IF v_swap.target_shift_id IS NULL THEN
      RAISE EXCEPTION 'target shift missing';
    END IF;

    IF NOT EXISTS (
      SELECT 1 FROM public.duty_schedules
      WHERE id = v_swap.target_shift_id AND user_id = v_swap.recipient_id
    ) THEN
      RAISE EXCEPTION 'target shift ownership changed, cannot accept swap';
    END IF;

    -- Read both schedules' types and dates BEFORE the swap so we can decide
    -- companion direction independently of ownership changes.
    SELECT shift_type,
           DATE(start_time AT TIME ZONE 'Asia/Manila')
      INTO v_target_type, v_target_date
      FROM public.duty_schedules
     WHERE id = v_swap.target_shift_id;

    SELECT shift_type,
           DATE(start_time AT TIME ZONE 'Asia/Manila')
      INTO v_requester_type, v_requester_date
      FROM public.duty_schedules
     WHERE id = v_swap.shift_id;

    -- Primary swap: transfer both schedules atomically
    UPDATE public.duty_schedules
    SET user_id = v_swap.recipient_id, swap_request_id = p_swap_id
    WHERE id = v_swap.shift_id;        -- requester's schedule → recipient

    UPDATE public.duty_schedules
    SET user_id = v_swap.requester_id, swap_request_id = p_swap_id
    WHERE id = v_swap.target_shift_id; -- recipient's schedule → requester

    -- ── Companion ON_CALL transfer ────────────────────────────────────────
    -- Direction A: target is PM → requester is the new PM holder.
    --   Find companion ON_CALL previously owned by the RECIPIENT and give it
    --   to the REQUESTER.
    IF v_target_type = 'pm' THEN
      SELECT id INTO v_companion_id
        FROM public.duty_schedules
       WHERE user_id    = v_swap.recipient_id
         AND shift_type = 'on_call'
         AND DATE(start_time AT TIME ZONE 'Asia/Manila') = v_target_date
       ORDER BY start_time
       LIMIT 1;

      IF v_companion_id IS NOT NULL THEN
        UPDATE public.duty_schedules
        SET user_id = v_swap.requester_id, swap_request_id = p_swap_id
        WHERE id = v_companion_id;

        UPDATE public.swap_requests
        SET status = 'rejected', updated_at = now()
        WHERE id <> p_swap_id
          AND status IN ('pending', 'admin_review')
          AND (
            shift_id        = v_companion_id
            OR target_shift_id = v_companion_id
          );
      END IF;

    -- Direction B: requester's schedule is PM → recipient is the new PM holder.
    --   Find companion ON_CALL previously owned by the REQUESTER and give it
    --   to the RECIPIENT.
    ELSIF v_requester_type = 'pm' THEN
      SELECT id INTO v_companion_id
        FROM public.duty_schedules
       WHERE user_id    = v_swap.requester_id
         AND shift_type = 'on_call'
         AND DATE(start_time AT TIME ZONE 'Asia/Manila') = v_requester_date
       ORDER BY start_time
       LIMIT 1;

      IF v_companion_id IS NOT NULL THEN
        UPDATE public.duty_schedules
        SET user_id = v_swap.recipient_id, swap_request_id = p_swap_id
        WHERE id = v_companion_id;

        UPDATE public.swap_requests
        SET status = 'rejected', updated_at = now()
        WHERE id <> p_swap_id
          AND status IN ('pending', 'admin_review')
          AND (
            shift_id        = v_companion_id
            OR target_shift_id = v_companion_id
          );
      END IF;
    END IF;
    -- ─────────────────────────────────────────────────────────────────────

    -- Accept the swap request
    UPDATE public.swap_requests
    SET status = 'accepted', updated_at = now()
    WHERE id = p_swap_id;

    -- Auto-reject all other pending swaps referencing either primary schedule
    UPDATE public.swap_requests
    SET status = 'rejected', updated_at = now()
    WHERE id <> p_swap_id
      AND status IN ('pending', 'admin_review')
      AND (
        shift_id           = v_swap.shift_id
        OR shift_id        = v_swap.target_shift_id
        OR target_shift_id = v_swap.shift_id
        OR target_shift_id = v_swap.target_shift_id
      );

    INSERT INTO public.swap_request_participants (swap_request_id, user_id, role)
    VALUES (p_swap_id, auth.uid(), 'approver')
    ON CONFLICT DO NOTHING;

    INSERT INTO public.notifications (user_id, actor_id, type, created_at)
    VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());

  -- ── REJECTED ──────────────────────────────────────────────────────────────
  ELSIF p_action = 'rejected' THEN

    IF NOT (
      v_swap.recipient_id = auth.uid()
      OR EXISTS (
        SELECT 1 FROM public.profiles p
        WHERE p.id = auth.uid()
          AND p.role IN ('admin', 'dispatcher')
      )
    ) THEN
      RAISE EXCEPTION 'permission denied';
    END IF;

    UPDATE public.swap_requests
    SET status = 'rejected', updated_at = now()
    WHERE id = p_swap_id;

    INSERT INTO public.swap_request_participants (swap_request_id, user_id, role)
    VALUES (p_swap_id, auth.uid(), 'approver')
    ON CONFLICT DO NOTHING;

    INSERT INTO public.notifications (user_id, actor_id, type, created_at)
    VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());

  -- ── ADMIN_REVIEW ──────────────────────────────────────────────────────────
  ELSE
    IF NOT (v_swap.requester_id = auth.uid()) THEN
      RAISE EXCEPTION 'permission denied';
    END IF;

    UPDATE public.swap_requests
    SET status = 'admin_review', updated_at = now()
    WHERE id = p_swap_id;

    INSERT INTO public.notifications (user_id, actor_id, type, created_at)
    VALUES (v_swap.requester_id, auth.uid(), 'swap_update', now());
  END IF;
END;
$$;

-- Re-grant after SECURITY DEFINER replace
GRANT EXECUTE ON FUNCTION public.respond_shift_swap(uuid, text) TO authenticated;
