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

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

  setup do
    on_exit(fn ->
      Application.delete_env(:ash_graphql, AshGraphql.Test.Domain)

      try do
        AshGraphql.TestHelpers.stop_ets()
      rescue
        _ ->
          :ok
      end
    end)
  end

  test "an update works" do
    post =
      AshGraphql.Test.Post
      |> Ash.Changeset.for_create(:create, text: "foobar")
      |> Ash.create!()

    resp =
      """
      mutation UpdatePost($id: ID!, $input: UpdatePostInput) {
        updatePost(id: $id, input: $input) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post.id,
          "input" => %{
            "text" => "barbuz"
          }
        }
      )

    assert {:ok, %{data: %{"updatePost" => %{"errors" => [], "result" => %{"text" => "barbuz"}}}}} =
             resp
  end

  test "an update with a managed relationship works" do
    resp =
      """
      mutation CreatePostWithComments($input: CreatePostWithCommentsInput) {
        createPostWithComments(input: $input) {
          result{
            id
            text
            comments(sort:{field:TEXT}){
              id
              text
            }
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "input" => %{
            "text" => "foobar",
            "comments" => [
              %{"text" => "foobar"},
              %{"text" => "barfoo"}
            ]
          }
        }
      )

    assert {:ok, result} = resp

    refute Map.has_key?(result, :errors)

    assert %{
             data: %{
               "createPostWithComments" => %{
                 "result" => %{
                   "id" => post_id,
                   "text" => "foobar",
                   "comments" => [
                     %{"id" => comment_id, "text" => "barfoo"},
                     %{"text" => "foobar"}
                   ]
                 }
               }
             }
           } = result

    resp =
      """
      mutation UpdatePostWithComments($id: ID!, $input: UpdatePostWithCommentsInput) {
        updatePostWithComments(id: $id, input: $input) {
          result{
            comments(sort:{field:TEXT}){
              id
              text
            }
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post_id,
          "input" => %{
            "comments" => [
              %{"text" => "barfoonew", "id" => comment_id}
            ]
          }
        }
      )

    assert {:ok, result} = resp

    refute Map.has_key?(result, :errors)

    assert %{
             data: %{
               "updatePostWithComments" => %{
                 "result" => %{
                   "comments" => [%{"id" => ^comment_id, "text" => "barfoonew"}]
                 }
               }
             }
           } = result
  end

  test "an update with a configured read action and no identity works" do
    post =
      AshGraphql.Test.Post
      |> Ash.Changeset.for_create(:create, text: "foobar", best: true)
      |> Ash.create!()

    resp =
      """
      mutation UpdateBestPost($input: UpdateBestPostInput) {
        updateBestPost(input: $input) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post.id,
          "input" => %{
            "text" => "barbuz"
          }
        }
      )

    assert {:ok,
            %{data: %{"updateBestPost" => %{"errors" => [], "result" => %{"text" => "barbuz"}}}}} =
             resp
  end

  test "an update with a configured read action and no identity works with an argument the same name as an attribute" do
    AshGraphql.Test.Post
    |> Ash.Changeset.for_create(:create, text: "foobar", best: true)
    |> Ash.create!()

    resp =
      """
      mutation UpdateBestPostArg($best: Boolean!, $input: UpdateBestPostArgInput) {
        updateBestPostArg(best: $best, input: $input) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "best" => true,
          "input" => %{
            "text" => "barbuz"
          }
        }
      )

    assert {:ok,
            %{
              data: %{"updateBestPostArg" => %{"errors" => [], "result" => %{"text" => "barbuz"}}}
            }} = resp
  end

  test "arguments are threaded properly" do
    post =
      AshGraphql.Test.Post
      |> Ash.Changeset.for_create(:create, text: "foobar", best: true)
      |> Ash.create!()

    resp =
      """
      mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID!) {
        updatePostConfirm(input: $input, id: $id) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post.id,
          "input" => %{
            "text" => "foobar",
            "confirmation" => "foobar2"
          }
        }
      )

    assert {:ok, result} = resp

    assert %{
             data: %{
               "updatePostConfirm" => %{"result" => nil, "errors" => [%{"message" => message}]}
             }
           } = result

    assert message =~ "confirmation did not match value"
  end

  test "root level error" do
    Application.put_env(:ash_graphql, AshGraphql.Test.Domain,
      graphql: [show_raised_errors?: true, root_level_errors?: true]
    )

    post =
      AshGraphql.Test.Post
      |> Ash.Changeset.for_create(:create, text: "foobar", best: true)
      |> Ash.create!()

    resp =
      """
      mutation UpdatePostConfirm($input: UpdatePostConfirmInput, $id: ID!) {
        updatePostConfirm(input: $input, id: $id) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post.id,
          "input" => %{
            "text" => "foobar",
            "confirmation" => "foobar2"
          }
        }
      )

    assert {:ok, result} = resp

    assert %{errors: [%{message: message}]} = result

    assert message =~ "confirmation did not match value"
  end

  test "referencing a hidden input is not allowed" do
    post =
      AshGraphql.Test.Post
      |> Ash.Changeset.for_create(:create, text: "foobar")
      |> Ash.create!()

    resp =
      """
      mutation UpdatePostWithHiddenInput($id: ID!, $input: UpdatePostWithHiddenInputInput) {
        updatePostWithHiddenInput(id: $id, input: $input) {
          result{
            text
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "id" => post.id,
          "input" => %{
            "score" => 10
          }
        }
      )

    assert {
             :ok,
             %{
               errors: [
                 %{
                   message:
                     "Argument \"input\" has invalid value $input.\nIn field \"score\": Unknown field."
                 }
               ]
             }
           } =
             resp
  end

  test "an update with a configured read action and no identity works in resource with simple data layer" do
    channel =
      AshGraphql.Test.Channel
      |> Ash.Changeset.for_create(:create, name: "test channel 1")
      |> Ash.create!()

    resp =
      """
      mutation UpdateChannel($channelId: ID!, $input: UpdateChannelInput!) {
        updateChannel(channelId: $channelId, input: $input) {
          result{
            channel {
              name
            }
          }
          errors{
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema,
        variables: %{
          "channelId" => channel.id,
          "input" => %{"name" => "test channel 2"}
        }
      )

    assert {:ok, result} = resp

    assert %{
             data: %{
               "updateChannel" => %{"result" => %{"channel" => %{"name" => "test channel 2"}}}
             }
           } =
             result
  end

  test "authenticateWithToken" do
    _user =
      AshGraphql.Test.User
      |> Ash.Changeset.for_create(:create, %{name: "Name"})
      |> Ash.create!(authorize?: false)

    resp =
      """
      mutation AuthenticateWithToken($token: String!) {
        authenticateWithToken(token: $token) {
          result {
            name
          }
          errors {
            message
          }
        }
      }
      """
      |> Absinthe.run(AshGraphql.Test.Schema, variables: %{"token" => "invalid-token"})

    assert {:ok,
            %{
              data: %{
                "authenticateWithToken" => %{
                  "errors" => [%{"message" => "test error"}],
                  "result" => nil
                }
              }
            }} = resp
  end

  describe "update action with args" do
    setup do
      post =
        AshGraphql.Test.Post
        |> Ash.Changeset.for_create(:create, text: "initial")
        |> Ash.create!()

      run = fn args ->
        """
        mutation {
          updatePostWithArg#{args} {
            result {
              text
            }
          }
        }
        """
        |> Absinthe.run(AshGraphql.Test.Schema)
      end

      expect_post = fn args, text ->
        assert {:ok, result} = run.(args)
        assert %{data: %{"updatePostWithArg" => %{"result" => %{"text" => ^text}}}} = result
      end

      expect_error = fn args, message ->
        assert {:ok, result} = run.(args)
        assert %{errors: [%{message: ^message}]} = result
      end

      [
        id: post.id,
        expect_post: expect_post,
        expect_error: expect_error
      ]
    end

    test "defines and uses an argument", %{id: id, expect_post: expect_post} do
      expect_post.(~s'(id: "#{id}", text: "nice")', "nice")
    end

    test "does not require optional arguments", %{id: id, expect_post: expect_post} do
      expect_post.(~s'(id: "#{id}")', "initial")
    end

    test "works together with input object", %{id: id, expect_post: expect_post} do
      expect_post.(~s'(id: "#{id}", text: "nice", input: {best: true})', "nice")
    end

    test "does not define input field for an argument", %{id: id, expect_error: expect_error} do
      expect_error.(
        ~s'(id: "#{id}", input: {text: "nice"})',
        ~s'Argument "input" has invalid value {text: "nice"}.\nIn field "text": Unknown field. Did you mean "text1" or "text2"?'
      )
    end
  end
end
