-- name: GetGroupMembers :many
SELECT * FROM group_members_expanded
WHERE CASE
      WHEN @include_system::bool THEN TRUE
      ELSE
        user_is_system = false
        END;

-- name: GetGroupMembersByGroupID :many
SELECT *
FROM group_members_expanded
WHERE group_id = @group_id
  -- Filter by system type
  AND CASE
      WHEN @include_system::bool THEN TRUE
      ELSE
        user_is_system = false
      END;

-- name: GetGroupMembersByGroupIDPaginated :many
SELECT
	*, COUNT(*) OVER() AS count
FROM
	group_members_expanded
WHERE
	group_members_expanded.group_id = @group_id
	AND CASE
		-- This allows using the last element on a page as effectively a cursor.
		-- This is an important option for scripts that need to paginate without
		-- duplicating or missing data.
		WHEN @after_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN (
			-- The pagination cursor is the last ID of the previous page.
			-- The query is ordered by the username field, so select all
			-- rows after the cursor.
			(LOWER(user_username)) > (
				SELECT
					LOWER(user_username)
				FROM
					group_members_expanded
				WHERE
					group_id = @group_id
					AND user_id = @after_id
			)
		)
		ELSE true
	END
	-- Start filters
	-- Filter by email or username
	AND CASE
		WHEN @search :: text != '' THEN (
			user_email ILIKE concat('%', @search, '%')
			OR user_username ILIKE concat('%', @search, '%')
		)
		ELSE true
	END
	-- Filter by name (display name)
	AND CASE
		WHEN @name :: text != '' THEN
			user_name ILIKE concat('%', @name, '%')
		ELSE true
	END
	-- Filter by status
	AND CASE
		-- @status needs to be a text because it can be empty, If it was
		-- user_status enum, it would not.
		WHEN cardinality(@status :: user_status[]) > 0 THEN
			user_status = ANY(@status :: user_status[])
		ELSE true
	END
	-- Filter by rbac_roles
	AND CASE
		-- @rbac_role allows filtering by rbac roles. If 'member' is included, show everyone, as
		-- everyone is a member.
		WHEN cardinality(@rbac_role :: text[]) > 0 AND 'member' != ANY(@rbac_role :: text[]) THEN
			user_rbac_roles && @rbac_role :: text[]
		ELSE true
	END
	-- Filter by last_seen
	AND CASE
		WHEN @last_seen_before :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN
			user_last_seen_at <= @last_seen_before
		ELSE true
	END
	AND CASE
		WHEN @last_seen_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN
			user_last_seen_at >= @last_seen_after
		ELSE true
	END
	-- Filter by created_at
	AND CASE
		WHEN @created_before :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN
			user_created_at <= @created_before
		ELSE true
	END
	AND CASE
		WHEN @created_after :: timestamp with time zone != '0001-01-01 00:00:00Z' THEN
			user_created_at >= @created_after
		ELSE true
	END
	-- Filter by system type
	AND CASE
		WHEN @include_system::bool THEN TRUE
		ELSE user_is_system = false
	END
	-- Filter by github.com user ID
	AND CASE
		WHEN @github_com_user_id :: bigint != 0 THEN
			user_github_com_user_id = @github_com_user_id
		ELSE true
	END
	-- Filter by login_type
	AND CASE
		WHEN cardinality(@login_type :: login_type[]) > 0 THEN
			user_login_type = ANY(@login_type :: login_type[])
		ELSE true
	END
	-- Filter by service account.
	AND CASE
		WHEN sqlc.narg('is_service_account') :: boolean IS NOT NULL THEN
			user_is_service_account = sqlc.narg('is_service_account') :: boolean
		ELSE true
	END
	-- End of filters
ORDER BY
	-- Deterministic and consistent ordering of all users. This is to ensure consistent pagination.
	LOWER(user_username) ASC OFFSET @offset_opt
LIMIT
	-- A null limit means "no limit", so 0 means return all
	NULLIF(@limit_opt :: int, 0);

-- name: GetGroupMembersCountByGroupID :one
-- Returns the total count of members in a group. Shows the total
-- count even if the caller does not have read access to ResourceGroupMember.
-- They only need ResourceGroup read access.
SELECT COUNT(*)
FROM group_members_expanded
WHERE group_id = @group_id
  -- Filter by system type
  AND CASE
      WHEN @include_system::bool THEN TRUE
      ELSE
        user_is_system = false
        END;

-- name: GetGroupMembersCountByGroupIDs :many
-- Returns the total member count for each of the given group IDs in a
-- single query. Used to avoid N+1 lookups when listing many groups. Like
-- GetGroupMembersCountByGroupID, the count is returned even when the
-- caller does not have read access to individual group members.
SELECT
	group_id,
	COUNT(*) AS member_count
FROM group_members_expanded
WHERE group_id = ANY(@group_ids :: uuid[])
	AND CASE
		WHEN @include_system::bool THEN TRUE
		ELSE user_is_system = false
	END
GROUP BY group_id;

-- InsertUserGroupsByID adds a user to all provided groups, if they exist.
-- name: InsertUserGroupsByID :many
WITH groups AS (
	SELECT
		id
	FROM
		groups
	WHERE
		groups.id = ANY(@group_ids :: uuid [])
)
INSERT INTO
	group_members (user_id, group_id)
SELECT
	@user_id,
	groups.id
FROM
	groups
-- If there is a conflict, the user is already a member
ON CONFLICT DO NOTHING
RETURNING group_id;

-- name: RemoveUserFromGroups :many
DELETE FROM
	group_members
WHERE
	user_id = @user_id AND
	group_id = ANY(@group_ids :: uuid [])
RETURNING group_id;

-- name: InsertGroupMember :exec
INSERT INTO
    group_members (user_id, group_id)
VALUES
    ($1, $2);

-- name: DeleteGroupMemberFromGroup :exec
DELETE FROM
	group_members
WHERE
	user_id = $1 AND
	group_id = $2;
