# SPDX-FileCopyrightText: 2020 Zach Daniel
#
# SPDX-License-Identifier: MIT

defmodule AshGraphql.PaginateTest do
  use ExUnit.Case, async: false

  require Ash.Query

  setup do
    on_exit(fn ->
      AshGraphql.TestHelpers.stop_ets()
    end)
  end

  describe "keyset pagination" do
    setup do
      letters = ["a", "b", "c", "d", "e"]

      for text <- letters do
        post =
          AshGraphql.Test.Post
          |> Ash.Changeset.for_create(:create, text: text, published: true)
          |> Ash.create!()

        for text <- letters do
          AshGraphql.Test.Comment
          |> Ash.Changeset.for_create(:create, text: text)
          |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
          |> Ash.create!()
        end
      end

      :ok
    end

    test "default_limit records are fetched" do
      doc = """
      query KeysetPaginatedPosts {
        keysetPaginatedPosts(sort: [{field: TEXT, order: ASC_NULLS_LAST}]) {
          count
          startKeyset
          endKeyset
          results{
            text
            keyset
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "keysetPaginatedPosts" => %{
                    "startKeyset" => start_keyset,
                    "endKeyset" => end_keyset,
                    "count" => 5,
                    "results" => [
                      %{"text" => "a", "keyset" => keyset},
                      %{"text" => "b"},
                      %{"text" => "c"},
                      %{"text" => "d"},
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)

      assert is_binary(keyset)
      assert is_binary(start_keyset)
      assert is_binary(end_keyset)
    end

    test "works if action has both offset and keyset pagination" do
      doc = """
      query OtherKeysetPaginatedPosts {
        otherKeysetPaginatedPosts(sort: [{field: TEXT, order: ASC_NULLS_LAST}]) {
          count
          startKeyset
          endKeyset
          results{
            text
            keyset
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "otherKeysetPaginatedPosts" => %{
                    "startKeyset" => start_keyset,
                    "endKeyset" => end_keyset,
                    "count" => 5,
                    "results" => [
                      %{"text" => "a", "keyset" => keyset},
                      %{"text" => "b"},
                      %{"text" => "c"},
                      %{"text" => "d"},
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)

      assert is_binary(keyset)
      assert is_binary(start_keyset)
      assert is_binary(end_keyset)
    end
  end

  describe "offset pagination" do
    setup do
      letters = ["a", "b", "c", "d", "e"]

      for text <- letters do
        post =
          AshGraphql.Test.Post
          |> Ash.Changeset.for_create(:create, text: text, published: true)
          |> Ash.create!()

        for text <- letters do
          AshGraphql.Test.Comment
          |> Ash.Changeset.for_create(:create, text: text)
          |> Ash.Changeset.manage_relationship(:post, post, type: :append_and_remove)
          |> Ash.create!()
        end
      end

      :ok
    end

    test "retruns pagination metadata" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 1, sort: [{field: TEXT}]) {
          count
          hasNextPage
          hasPreviousPage
          pageNumber
          lastPage
          limit
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "hasNextPage" => true,
                    "hasPreviousPage" => false,
                    "pageNumber" => 1,
                    "lastPage" => 5,
                    "limit" => 1,
                    "results" => [
                      %{"text" => "a"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "pagination metadata is correct when offset is 2" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 1, offset: 2, sort: [{field: TEXT}]) {
          count
          hasNextPage
          hasPreviousPage
          pageNumber
          lastPage
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "hasNextPage" => true,
                    "hasPreviousPage" => true,
                    "pageNumber" => 3,
                    "lastPage" => 5,
                    "results" => [
                      %{"text" => "c"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "pagination metadata is correct when offset is 4" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 1, offset: 4, sort: [{field: TEXT}]) {
          count
          hasNextPage
          hasPreviousPage
          pageNumber
          lastPage
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "hasNextPage" => false,
                    "hasPreviousPage" => true,
                    "pageNumber" => 5,
                    "lastPage" => 5,
                    "results" => [
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "pagination metadata is correct when the is no limit" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(sort: [{field: TEXT}]) {
          count
          hasNextPage
          hasPreviousPage
          pageNumber
          lastPage
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "hasNextPage" => false,
                    "hasPreviousPage" => false,
                    "pageNumber" => 1,
                    "lastPage" => 1,
                    "results" => [
                      %{"text" => "a"},
                      %{"text" => "b"},
                      %{"text" => "c"},
                      %{"text" => "d"},
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "default_limit records are fetched" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(sort: [{field: TEXT}]) {
          count
          results{
            text
          }
          hasNextPage
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "hasNextPage" => false,
                    "results" => [
                      %{"text" => "a"},
                      %{"text" => "b"},
                      %{"text" => "c"},
                      %{"text" => "d"},
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "without limit all records are fetched" do
      doc = """
      query PaginatedPostsLimitNotRequired {
        paginatedPostsLimitNotRequired(sort: [{field: TEXT}]) {
          count
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPostsLimitNotRequired" => %{
                    "count" => 5,
                    "results" => [
                      %{"text" => "a"},
                      %{"text" => "b"},
                      %{"text" => "c"},
                      %{"text" => "d"},
                      %{"text" => "e"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "the first can be fetched" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 1, sort: [{field: TEXT}]) {
          count
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "results" => [
                      %{"text" => "a"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "the count can be fetched on its own" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 1) {
          count
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "it can be paged through" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 2, offset: 2, sort: [{field: TEXT}]) {
          count
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "results" => [
                      %{"text" => "c"},
                      %{"text" => "d"}
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "related items can be requested while paginating" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 2, offset: 2, sort: [{field: TEXT}]) {
          count
          results{
            text
            comments(sort:[{field: TEXT}]){
              text
            }
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "results" => [
                      %{
                        "text" => "c",
                        "comments" => [
                          %{"text" => "a"},
                          %{"text" => "b"},
                          %{"text" => "c"},
                          %{"text" => "d"},
                          %{"text" => "e"}
                        ]
                      },
                      %{
                        "text" => "d",
                        "comments" => [
                          %{"text" => "a"},
                          %{"text" => "b"},
                          %{"text" => "c"},
                          %{"text" => "d"},
                          %{"text" => "e"}
                        ]
                      }
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end

    test "related items can be limited and offset while paginating" do
      doc = """
      query PaginatedPosts {
        paginatedPosts(limit: 2, offset: 2, sort: [{field: TEXT}]) {
          count
          results{
            text
            comments(limit: 2, offset: 2, sort:[{field: TEXT}]){
              text
            }
          }
        }
      }
      """

      assert {:ok,
              %{
                data: %{
                  "paginatedPosts" => %{
                    "count" => 5,
                    "results" => [
                      %{
                        "text" => "c",
                        "comments" => [
                          %{"text" => "c"},
                          %{"text" => "d"}
                        ]
                      },
                      %{
                        "text" => "d",
                        "comments" => [
                          %{"text" => "c"},
                          %{"text" => "d"}
                        ]
                      }
                    ]
                  }
                }
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end
  end

  describe "pagination errors" do
    test "required limit without explicit value" do
      doc = """
      query PaginatedPosts {
        paginatedPostsWithoutLimit(sort: [{field: TEXT}]) {
          count
          results{
            text
          }
        }
      }
      """

      assert {:ok,
              %{
                errors: [
                  %{
                    locations: [%{column: 3, line: 2}],
                    message: "In argument \"limit\": Expected type \"Int!\", found null."
                  }
                ]
              }} = Absinthe.run(doc, AshGraphql.Test.Schema)
    end
  end
end
