defmodule Level.PaginationTest do
  use Level.DataCase, async: true

  import Ecto.Query

  alias Level.Pagination
  alias Level.Pagination.Args
  alias Level.Pagination.Result
  alias Level.Schemas.SpaceUser

  describe "fetch_result/3" do
    setup do
      {:ok, %{space_user: owner, space: space}} = create_user_and_space()
      members = [owner | create_members(space, 3)]
      sorted_member = Enum.sort_by(members, fn m -> m.id end)
      base_query = from m in SpaceUser, where: m.space_id == ^space.id
      {:ok, users: sorted_member, base_query: base_query, space: space}
    end

    test "only first param provided", %{users: users, base_query: base_query} do
      args = %Args{
        first: 2,
        before: nil,
        after: nil,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:ok, %Result{edges: edges}} = Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.take(users, 2))
    end

    test "first with after cursor provided", %{users: users, base_query: base_query} do
      args = %Args{
        first: 2,
        before: nil,
        after: Enum.at(users, 0).id,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:ok, %Result{edges: edges, page_info: _page_info}} =
        Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.slice(users, 1..2))
    end

    test "only last provider", %{users: users, base_query: base_query} do
      args = %Args{
        last: 2,
        before: nil,
        after: nil,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:ok, %Result{edges: edges, page_info: _page_info}} =
        Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.slice(users, 2..3))
    end

    test "last with before cursor provided", %{users: users, base_query: base_query} do
      args = %Args{
        last: 2,
        before: Enum.at(users, 3).id,
        after: nil,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:ok, %Result{edges: edges, page_info: _page_info}} =
        Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.slice(users, 1..2))
    end

    test "last with before and after cursors provided", %{users: users, base_query: base_query} do
      args = %Args{
        last: 2,
        after: Enum.at(users, 1).id,
        before: Enum.at(users, 3).id,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:ok, %Result{edges: edges, page_info: _page_info}} =
        Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.slice(users, 2..2))
    end

    test "descending order", %{users: users, base_query: base_query} do
      args = %Args{
        first: 2,
        before: nil,
        after: nil,
        order_by: %{
          field: :id,
          direction: :desc
        }
      }

      {:ok, %Result{edges: edges}} = Pagination.fetch_result(base_query, args)

      assert map_edge_ids(edges) == map_ids(Enum.take(Enum.reverse(users), 2))
    end

    test "fails when neither first nor last is non-nil", %{base_query: base_query} do
      args = %Args{
        first: nil,
        last: nil,
        before: nil,
        after: nil,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:error, "You must provide either a `first` or `last` value"} =
        Pagination.fetch_result(base_query, args)
    end

    test "fails when first and last is set", %{base_query: base_query} do
      args = %Args{
        first: 10,
        last: 10,
        before: nil,
        after: nil,
        order_by: %{
          field: :id,
          direction: :asc
        }
      }

      {:error, "You must provide either a `first` or `last` value"} =
        Pagination.fetch_result(base_query, args)
    end
  end

  defp create_members(space, count, list \\ [])
  defp create_members(_space, count, list) when count < 1, do: list

  defp create_members(space, count, list) do
    {:ok, %{space_user: space_user}} = create_space_member(space)
    create_members(space, count - 1, [space_user | list])
  end

  defp map_edge_ids(edges) do
    Enum.map(edges, fn edge -> edge.node.id end)
  end

  defp map_ids(nodes) do
    Enum.map(nodes, fn node -> node.id end)
  end
end
