defmodule Ash.Test.ReactorUpdateTest do
  @moduledoc false
  use ExUnit.Case, async: true

  alias Ash.Test.Domain

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

    ets do
      private? true
    end

    attributes do
      uuid_primary_key :id
      attribute :title, :string, allow_nil?: false, public?: true
      attribute :sub_title, :string, public?: true
      attribute :published, :boolean, default: false, public?: true
    end

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

      update :undo_update_post do
        require_atomic? false

        argument :changeset, :struct do
          constraints instance_of: Ash.Changeset
        end

        change fn changeset, _ ->
          original_changeset = Ash.Changeset.get_argument(changeset, :changeset)

          [:title, :sub_title, :published]
          |> Enum.reduce(changeset, fn key, changeset ->
            original_value = Ash.Changeset.get_data(original_changeset, key)
            Ash.Changeset.change_attribute(changeset, key, original_value)
          end)
        end
      end
    end

    code_interface do
      define :create
      define :get, action: :read, get_by: :id
    end
  end

  test "it can update a post" do
    defmodule SimpleUpdatePostReactor do
      @moduledoc false
      use Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      input :post

      update :publish_post, Post, :update do
        initial(input(:post))
        inputs(%{published: value(true)})
      end
    end

    {:ok, %{published: false} = original_post} =
      Post.create(%{title: "Title", sub_title: "Sub-title"})

    assert {:ok, post} =
             Reactor.run(SimpleUpdatePostReactor, %{post: original_post}, %{}, async?: false)

    assert post.published
  end

  test "it defaults to the primary action when the action is not supplied" do
    defmodule InferredActionNameUpdatePostReactor do
      @moduledoc false
      use Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      input :post
      input :new_title

      update :update_post, Post do
        inputs(%{title: input(:new_title)})
        initial(input(:post))
      end
    end

    {:ok, original_post} =
      Post.create(%{title: "Title", sub_title: "Sub-title"})

    assert {:ok, post} =
             Reactor.run(
               InferredActionNameUpdatePostReactor,
               %{post: original_post, new_title: "New Title"},
               %{},
               async?: false
             )

    assert post.title == "New Title"
  end

  test "it merges multiple `inputs` entities together" do
    defmodule MergedInputsCreatePostReactor do
      @moduledoc false
      use Ash.Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      input :post
      input :new_title
      input :new_sub_title

      update :update_post, Post, :update do
        initial(input(:post))
        inputs(%{title: input(:new_title)})
        inputs(%{sub_title: input(:new_sub_title)})
      end
    end

    {:ok, original_post} =
      Post.create(%{title: "Title", sub_title: "Sub-title"})

    assert {:ok, post} =
             Reactor.run(
               MergedInputsCreatePostReactor,
               %{post: original_post, new_title: "New Title", new_sub_title: "New Sub-title"},
               %{},
               async?: false
             )

    assert post.title == "New Title"
    assert post.sub_title == "New Sub-title"
  end

  test "it can undo the update on error" do
    defmodule UndoingUpdateReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain(Domain)
      end

      input :post
      input :new_title

      update :update_post, Post, :update do
        initial(input(:post))
        inputs(%{title: input(:new_title)})
        undo :always
        undo_action(:undo_update_post)
      end

      step :fail do
        wait_for :update_post

        run fn _, _ ->
          raise "hell"
        end
      end
    end

    {:ok, post} = Post.create(%{title: "Title"})

    assert {:error, _} =
             Reactor.run(
               UndoingUpdateReactor,
               %{
                 post: post,
                 new_title: "New title"
               },
               %{},
               async?: false
             )

    post_run_post = Post.get!(post.id)
    assert post_run_post.title == "Title"
  end
end
