defmodule Ash.Actions.PaginationTest do
  use ExUnit.Case, async: true
  use Mimic

  require Ash.Query

  alias Ash.Test.Domain, as: Domain

  defmodule Post do
    @moduledoc false
    use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets

    ets do
      private?(true)
    end

    attributes do
      uuid_primary_key :id

      attribute :user_id, :uuid do
        public?(true)
      end

      attribute :body, :string do
        public?(true)
      end
    end

    actions do
      default_accept :*
      defaults [:destroy, create: :*, update: :*]

      read :read do
        primary? true
        pagination offset?: true, required?: true, default_limit: 25
      end
    end

    relationships do
      belongs_to :user, Ash.Actions.PaginationTest.User, define_attribute?: false, public?: true
    end
  end

  defmodule User do
    @moduledoc false
    use Ash.Resource, domain: Domain, data_layer: Ash.DataLayer.Ets

    ets do
      private?(true)
    end

    actions do
      default_accept :*

      read :offset do
        pagination offset?: true, countable: true, required?: true
      end

      read :optional_offset do
        pagination offset?: true, countable: true, required?: false
      end

      read :offset_countable_by_default do
        pagination offset?: true, countable: :by_default, required?: false
      end

      read :required_offset_with_default do
        pagination offset?: true, countable: true, required?: false, default_limit: 25
      end

      read :keyset do
        pagination keyset?: true, countable: true
      end

      read :keyset_before_action do
        prepare(
          before_action(fn query, _ -> Ash.Query.filter(query, name in ["0", "1", "2"]) end)
        )

        pagination keyset?: true, countable: true
      end

      read :optional_keyset do
        pagination keyset?: true, countable: true, required?: false
      end

      read :keyset_countable_by_default do
        pagination keyset?: true, countable: :by_default, required?: false
      end

      read :required_keyset_with_default do
        pagination keyset?: true, countable: true, required?: false, default_limit: 25
      end

      read :both_required do
        primary? true
        pagination keyset?: true, offset?: true, countable: true
      end

      read :both_optional do
        pagination keyset?: true, offset?: true, countable: true, default_limit: 25
      end

      defaults create: :*, update: :*
    end

    attributes do
      uuid_primary_key :id

      attribute :name, :string do
        public?(true)
      end

      attribute :subname, :string do
        public?(true)
      end
    end

    aggregates do
      count :count_of_posts, :posts do
        public? true
      end
    end

    calculations do
      calculate :name_with_arg, :string, expr(name) do
        public?(true)

        argument :does_nothing, :boolean do
          allow_nil? false
        end
      end
    end

    relationships do
      has_many :posts, Post do
        public?(true)
      end
    end
  end

  test "pagination is required by default" do
    assert_raise Ash.Error.Invalid, ~r/Pagination is required/, fn ->
      Ash.read!(User, page: false)
    end
  end

  test "a default limit allows not specifying page parameters" do
    assert_raise Ash.Error.Invalid, ~r/Limit is required/, fn ->
      Ash.read!(User, page: [offset: 1])
    end

    Ash.read!(User, action: :required_offset_with_default)
  end

  describe "offset pagination" do
    setup do
      for i <- 0..9 do
        user = Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}"}))

        if i != 0 do
          for x <- 1..i do
            Ash.create!(
              Ash.Changeset.for_create(Post, :create, %{body: "#{i}-#{x}", user_id: user.id})
            )
          end
        end
      end

      :ok
    end

    test "can be limited" do
      assert Enum.count(Ash.read!(User, action: :optional_offset, page: false)) == 10

      assert Enum.count(Ash.read!(User, action: :optional_offset, page: [limit: 5]).results) ==
               5
    end

    test "can be offset" do
      assert Enum.count(Ash.read!(User, action: :optional_offset, page: false)) == 10

      assert Enum.count(
               Ash.read!(User, action: :optional_offset, page: [offset: 5, limit: 5]).results
             ) == 5
    end

    test "can include a full count" do
      assert Ash.read!(User, action: :optional_offset, page: [limit: 1, count: true]).count ==
               10
    end

    test "can include a full count with an offset" do
      assert Ash.read!(User,
               action: :optional_offset,
               page: [offset: 5, limit: 1, count: true]
             ).count ==
               10
    end

    test "can default to including a count" do
      assert Ash.read!(User, action: :offset_countable_by_default, page: [limit: 1]).count ==
               10
    end

    test "count is not included by default otherwise" do
      assert is_nil(Ash.read!(User, action: :optional_offset, page: [limit: 1]).count)
    end

    test "`count: false` prevents the count from occurring even if it is on `by_default`" do
      assert is_nil(
               Ash.read!(User,
                 action: :offset_countable_by_default,
                 page: [limit: 1, count: false]
               ).count
             )
    end

    test "pagination works with a sort applied" do
      names =
        User
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [offset: 5, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8", "9"]
    end

    test "pagination works with a reversed sort applied" do
      names =
        User
        |> Ash.Query.sort(name: :desc)
        |> Ash.read!(page: [offset: 5, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["4", "3", "2", "1", "0"]
    end

    test "pagination works with a filter" do
      names =
        User
        |> Ash.Query.sort(name: :desc)
        |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
        |> Ash.read!(page: [offset: 1, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["3", "2", "1", "0"]
    end

    test "the next page can be fetched" do
      assert %{results: [%{name: "3"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [offset: 1, limit: 1])

      assert %{results: [%{name: "2"}]} = Ash.page!(page, :next)
    end

    test "the previous page can be fetched" do
      assert %{results: [%{name: "3"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [offset: 1, limit: 1])

      assert %{results: [%{name: "4"}]} = Ash.page!(page, :prev)
    end

    test "the first page can be fetched" do
      assert %{results: [%{name: "2"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [offset: 2, limit: 1])

      assert %{results: [%{name: "4"}]} = Ash.page!(page, :first)
    end

    test "the last page can be fetched if the count was requested" do
      assert %{results: [%{name: "3"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [offset: 1, limit: 1, count: true])

      assert %{results: [%{name: "0"}]} = Ash.page!(page, :last)
    end

    test "the same page can be re-fetched" do
      assert %{results: [%{name: "3"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [offset: 1, limit: 1, count: true])

      assert %{results: [%{name: "3"}]} = Ash.page!(page, :self)
    end
  end

  describe "keyset pagination with nil fields" do
    setup do
      users =
        for i <- 0..9 do
          if rem(i, 2) == 0 do
            Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}", subname: "#{i}"}))
          else
            Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}"}))
          end
        end

      [users: users]
    end

    test "can be paged through when a non-nil value is the keyset" do
      %{results: first_results} =
        User
        |> Ash.Query.sort([:subname, :name])
        |> Ash.Query.select([:name])
        |> Ash.read!(action: :keyset, page: [limit: 5])

      assert Enum.map(first_results, & &1.name) == ~w(0 2 4 6 8)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort([:subname, :name])
        |> Ash.Query.select([:name])
        |> Ash.read!(action: :keyset, page: [limit: 5, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(1 3 5 7 9)
    end

    test "can be paged through  when a nil value is the keyset" do
      %{results: first_results} =
        User
        |> Ash.Query.sort([:subname, :name])
        |> Ash.read!(action: :keyset, page: [limit: 6])

      assert Enum.map(first_results, & &1.name) == ~w(0 2 4 6 8 1)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort([:subname, :name])
        |> Ash.read!(action: :keyset, page: [limit: 6, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(3 5 7 9)
    end

    test "can be paged through when a non-nil value is the keyset using asc_nils_first" do
      %{results: first_results} =
        User
        |> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5])

      assert Enum.map(first_results, & &1.name) == ~w(1 3 5 7 9)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(0 2 4 6 8)
    end

    test "can be paged through when a non-nil value is the keyset using desc_nils_first" do
      %{results: first_results} =
        User
        |> Ash.Query.sort(subname: :desc_nils_first, name: :desc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5])

      assert Enum.map(first_results, & &1.name) == ~w(9 7 5 3 1)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort(subname: :desc_nils_first, name: :desc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(8 6 4 2 0)
    end

    test "can be paged through  when a nil value is the keyset using asc_nils_first" do
      %{results: first_results} =
        User
        |> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 6])

      assert Enum.map(first_results, & &1.name) == ~w(1 3 5 7 9 0)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort(subname: :asc_nils_first, name: :asc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(2 4 6 8)
    end

    test "can be paged through  when a nil value is the keyset using desc_nils_first" do
      %{results: first_results} =
        User
        |> Ash.Query.sort(subname: :desc_nils_first, name: :desc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 6])

      assert Enum.map(first_results, & &1.name) == ~w(9 7 5 3 1 8)

      keyset =
        first_results |> List.last(first_results) |> Map.get(:__metadata__) |> Map.get(:keyset)

      %{results: second_results} =
        User
        |> Ash.Query.sort(subname: :desc_nils_first, name: :desc_nils_first)
        |> Ash.read!(action: :keyset, page: [limit: 5, after: keyset])

      assert Enum.map(second_results, & &1.name) == ~w(6 4 2 0)
    end
  end

  describe "keyset pagination" do
    setup do
      users =
        for i <- 0..9 do
          user = Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}"}))

          if i != 0 do
            for x <- 1..i do
              Ash.create!(
                Ash.Changeset.for_create(Post, :create, %{body: "#{i}-#{x}", user_id: user.id})
              )
            end
          end
        end

      [users: users]
    end

    test "can be limited" do
      assert Enum.count(Ash.read!(User, action: :optional_keyset, page: false)) == 10

      assert Enum.count(Ash.read!(User, action: :optional_keyset, page: [limit: 5]).results) ==
               5
    end

    test "can include a full count" do
      assert Ash.read!(User, action: :optional_keyset, page: [limit: 1, count: true]).count ==
               10
    end

    test "can include a full count with a sort and limit" do
      assert 10 =
               User
               |> Ash.Query.sort(:name)
               |> Ash.read!(action: :optional_keyset, page: [limit: 1, count: true])
               |> Map.get(:count)
    end

    test "can default to including a count" do
      assert Ash.read!(User, action: :keyset_countable_by_default, page: [limit: 1]).count ==
               10
    end

    test "count is not included by default otherwise" do
      assert is_nil(Ash.read!(User, action: :optional_keyset, page: [limit: 1]).count)
    end

    test "`count: false` prevents the count from occurring even if it is on `by_default`" do
      assert is_nil(
               Ash.read!(User,
                 action: :keyset_countable_by_default,
                 page: [limit: 1, count: false]
               ).count
             )
    end

    test "can ask for records after a specific keyset" do
      %{results: [%{id: id, __metadata__: %{keyset: keyset}}]} =
        Ash.read!(User, action: :keyset, page: [limit: 1])

      %{results: [%{id: next_id}]} =
        Ash.read!(User, action: :keyset, page: [limit: 1, after: keyset])

      refute id == next_id
    end

    test "can get the full count when asking for records after a specific keyset" do
      %{results: [%{__metadata__: %{keyset: keyset}}], count: 10} =
        Ash.read!(User, action: :keyset, page: [count: true, limit: 1])

      assert %{count: 10} =
               Ash.read!(User, action: :keyset, page: [count: true, limit: 1, after: keyset])
    end

    test "can get the full count when asking for records after a specific keyset use the query after applying `before_action` hooks" do
      %{results: [%{__metadata__: %{keyset: keyset}}], count: 3} =
        Ash.read!(User, action: :keyset_before_action, page: [count: true, limit: 1])

      assert %{count: 3} =
               Ash.read!(User,
                 action: :keyset_before_action,
                 page: [count: true, limit: 1, after: keyset]
               )
    end

    test "an invalid keyset returns an appropriate error" do
      assert_raise(Ash.Error.Invalid, ~r/Invalid value provided as a keyset/, fn ->
        Ash.read!(User, action: :keyset, page: [limit: 1, after: "~"])
      end)
    end

    test "can ask for records before a specific keyset" do
      %{results: [%{id: id, __metadata__: %{keyset: keyset}}]} =
        Ash.read!(User, action: :keyset, page: [limit: 1])

      %{results: [%{id: next_id, __metadata__: %{keyset: keyset2}}]} =
        Ash.read!(User, action: :keyset, page: [limit: 1, after: keyset])

      refute id == next_id

      %{results: [%{id: before_id}]} =
        Ash.read!(User, action: :keyset, page: [limit: 1, before: keyset2])

      assert id == before_id
    end

    test "can ask for records before a specific keyset, with the sort order honored" do
      %{results: users} =
        User |> Ash.Query.sort(:name) |> Ash.read!(action: :keyset, page: [limit: 100])

      users = Enum.sort_by(users, & &1.name)
      last_user = List.last(users)

      %{results: results} =
        User
        |> Ash.Query.sort(:name)
        |> Ash.read!(action: :keyset, page: [limit: 2, before: last_user.__metadata__.keyset])

      assert Enum.map(results, & &1.name) == [
               "7",
               "8"
             ]
    end

    test "can ask for records before a specific keyset, with the full count shown" do
      %{results: users} =
        User |> Ash.Query.sort(:name) |> Ash.read!(action: :keyset, page: [limit: 100])

      users = Enum.sort_by(users, & &1.name)
      last_user = List.last(users)
      assert Enum.count(users) == 10

      assert %{count: 10} =
               User
               |> Ash.Query.sort(:name)
               |> Ash.read!(
                 action: :keyset,
                 page: [count: true, limit: 2, before: last_user.__metadata__.keyset]
               )
    end

    test "pagination works with a sort applied" do
      page =
        User
        |> Ash.Query.filter(name == "4")
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8", "9"]
    end

    test "pagination works with a :desc sort applied" do
      page =
        User
        |> Ash.Query.filter(name == "4")
        |> Ash.Query.sort(name: :desc)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8", "9"]
    end

    test "pagination works with a sort applied that uses an aggregate" do
      page =
        User
        |> Ash.Query.filter(count_of_posts == 4)
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      page =
        User
        |> Ash.Query.sort(:count_of_posts)
        |> Ash.read!(page: [after: keyset, limit: 4])

      names =
        page
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8"]
      assert page.more?
    end

    test "pagination more? is false when there are no more records" do
      page =
        User
        |> Ash.Query.filter(count_of_posts == 5)
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      page =
        User
        |> Ash.Query.sort(:count_of_posts)
        |> Ash.read!(page: [after: keyset, limit: 4])

      names =
        page
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["6", "7", "8", "9"]
      refute page.more?
    end

    test "pagination works with a sort applied that uses an aggregate using `before`" do
      page =
        User
        |> Ash.Query.filter(count_of_posts == 4)
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      page =
        User
        |> Ash.Query.sort(:count_of_posts)
        |> Ash.read!(page: [before: keyset, limit: 3])

      names =
        page
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["1", "2", "3"]
      assert page.more?

      page =
        User
        |> Ash.Query.sort(:count_of_posts)
        |> Ash.read!(page: [after: keyset, limit: 4])

      names =
        page
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8"]
      assert page.more?
    end

    test "pagination more? is false when there are no more records using `before`" do
      page =
        User
        |> Ash.Query.filter(count_of_posts == 4)
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      page =
        User
        |> Ash.Query.sort(:count_of_posts)
        |> Ash.read!(page: [before: keyset, limit: 4])

      names =
        page
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["0", "1", "2", "3"]
      refute page.more?
    end

    test "pagination works with a sort applied that uses an aggregate desc" do
      User
      |> Ash.Query.load(:count_of_posts)
      |> Ash.read!(page: [limit: 10])
      |> Map.get(:results)
      |> Enum.map(&{&1.name, &1.count_of_posts})

      page =
        User
        |> Ash.Query.filter(count_of_posts == 4)
        |> Ash.Query.sort(:name)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(count_of_posts: :desc)
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["3", "2", "1", "0"]
    end

    test "pagination works with a sort applied that uses a calculation with arguments" do
      page =
        User
        |> Ash.Query.filter(name_with_arg(does_nothing: true) == "4")
        |> Ash.Query.sort(name_with_arg: %{does_nothing: true})
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(name_with_arg: %{does_nothing: true})
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["5", "6", "7", "8", "9"]
    end

    test "pagination utilities work with a sort applied that uses a calculation with arguments" do
      assert %{results: [%{name_with_arg: "5"}]} =
               User
               |> Ash.Query.sort(name_with_arg: %{does_nothing: true})
               |> Ash.Query.load(name_with_arg: %{does_nothing: true})
               |> Ash.read!(page: [limit: 1, offset: 4])
               |> Ash.page!(:next)
    end

    test "pagination works with a sort applied that uses a calculation desc" do
      page =
        User
        |> Ash.Query.filter(name_with_arg(does_nothing: true) == "4")
        |> Ash.Query.sort(name_with_arg: {%{does_nothing: true}, :desc})
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(name_with_arg: {%{does_nothing: true}, :desc})
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["3", "2", "1", "0"]
    end

    test "pagination works with a reversed sort applied" do
      page =
        User
        |> Ash.Query.filter(name == "5")
        |> Ash.Query.sort(name: :desc)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(name: :desc)
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["4", "3", "2", "1", "0"]
    end

    test "pagination works with a filter" do
      page =
        User
        |> Ash.Query.filter(name == "5")
        |> Ash.Query.sort(name: :desc)
        |> Ash.read!(page: [limit: 1])

      keyset = Enum.at(page.results, 0).__metadata__.keyset

      names =
        User
        |> Ash.Query.sort(name: :desc)
        |> Ash.Query.filter(name != "4")
        |> Ash.read!(page: [after: keyset, limit: 5])
        |> Map.get(:results)
        |> Enum.map(& &1.name)

      assert names == ["3", "2", "1", "0"]
    end

    test "the next page can be fetched" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [limit: 1])

      assert %{results: [%{name: "3"}]} = Ash.page!(page, :next)
    end

    test "the previous page can be fetched" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [limit: 1], action: :optional_keyset)

      assert %{results: [%{name: "3"}]} = page = Ash.page!(page, :next)
      assert %{results: [%{name: "4"}]} = Ash.page!(page, :prev)
    end

    test "the first page can be fetched" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [limit: 1])

      assert %{results: [%{name: "3"}]} = page = Ash.page!(page, :next)
      assert %{results: [%{name: "4"}]} = Ash.page!(page, :first)
    end

    test "the same page can be re-fetched" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(page: [limit: 1])

      assert %{results: [%{name: "3"}]} = page = Ash.page!(page, :next)
      assert %{results: [%{name: "3"}]} = Ash.page!(page, :self)
    end

    test "the prev request right after the initial query remains the same as the initial result (like offset pagination)" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.read!(action: :keyset, page: [limit: 1])

      assert %{results: [%{name: "4"}]} = Ash.page!(page, :prev)
    end
  end

  describe "when both are supported" do
    setup do
      for i <- 0..9 do
        Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}"}))
      end

      :ok
    end

    test "it defaults to keyset pagination" do
      assert %Ash.Page.Keyset{} = Ash.read!(User, action: :both_optional, page: [limit: 10])
    end

    test "it adds a keyset to the records, even though it returns an offset page" do
      for result <- Ash.read!(User, action: :both_optional, page: [limit: 10]).results do
        refute is_nil(result.__metadata__.keyset)
      end
    end
  end

  describe "loading with pagination" do
    test "it does not paginate loads" do
      user = Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "user"}))
      Ash.create!(Ash.Changeset.for_create(Post, :create, %{user_id: user.id}))

      assert [_ | _] =
               user
               |> Ash.load!([posts: :user], tenant: nil, actor: nil)
               |> Map.get(:posts)
    end
  end

  describe "Query.page/2" do
    setup do
      users =
        for i <- 0..9 do
          user = Ash.create!(Ash.Changeset.for_create(User, :create, %{name: "#{i}"}))

          if i != 0 do
            for x <- 1..i do
              Ash.create!(
                Ash.Changeset.for_create(Post, :create, %{body: "#{i}-#{x}", user_id: user.id})
              )
            end
          end
        end

      [users: users]
    end

    test "works with offset pagination" do
      assert User
             |> Ash.Query.page(false)
             |> Ash.read!(action: :optional_offset)
             |> Enum.count() == 10

      assert User
             |> Ash.Query.page(limit: 5)
             |> Ash.read!(action: :optional_offset)
             |> Map.fetch!(:results)
             |> Enum.count() == 5
    end

    test "works with keyset pagination" do
      assert User
             |> Ash.Query.page(false)
             |> Ash.read!(action: :optional_keyset)
             |> Enum.count() == 10

      assert User
             |> Ash.Query.page(limit: 5)
             |> Ash.read!(action: :optional_keyset)
             |> Map.fetch!(:results)
             |> Enum.count() == 5
    end

    test "allows retrieving the pages with Ash.page!/2" do
      assert %{results: [%{name: "4"}]} =
               page =
               User
               |> Ash.Query.sort(name: :desc)
               |> Ash.Query.filter(name in ["4", "3", "2", "1", "0"])
               |> Ash.Query.page(limit: 1)
               |> Ash.read!()

      assert %{results: [%{name: "3"}]} = Ash.page!(page, :next)
    end

    test "gets overridden by opts" do
      assert User
             |> Ash.Query.page(limit: 5)
             |> Ash.read!(page: [limit: 3])
             |> Map.fetch!(:results)
             |> Enum.count() == 3
    end
  end

  describe "counting" do
    test "counts synchronously when async is disabled" do
      disable_async? = Application.get_env(:ash, :disable_async?)
      Application.put_env(:ash, :disable_async?, true)
      on_exit(fn -> Application.put_env(:ash, :disable_async?, disable_async?) end)

      reject(&Ash.ProcessHelpers.async/2)

      assert Ash.read!(User, action: :optional_offset, page: [limit: 1, count: true]).count ==
               0
    end

    test "counts asynchronously when async is enabled" do
      disable_async? = Application.get_env(:ash, :disable_async?)
      Application.put_env(:ash, :disable_async?, false)
      on_exit(fn -> Application.put_env(:ash, :disable_async?, disable_async?) end)

      stub(Ash.DataLayer, :in_transaction?, fn _ -> false end)

      stub(Ash.DataLayer, :can?, fn
        :async_engine, _ -> true
        feature, resource -> Mimic.call_original(Ash.DataLayer, :can?, [feature, resource])
      end)

      expect(
        Ash.ProcessHelpers,
        :async,
        1,
        &Mimic.call_original(Ash.ProcessHelpers, :async, [&1, &2])
      )

      assert Ash.read!(User, action: :optional_offset, page: [limit: 1, count: true]).count ==
               0
    end
  end
end
