defmodule Ash.Test.ReactorDestroyTest 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: :*]

      create :undo_destroy do
        argument :record, :struct do
          allow_nil? false
          constraints instance_of: Post
        end

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

          [:id, :title, :sub_title, :published]
          |> Enum.reduce(changeset, fn key, changeset ->
            value = Map.get(record, key)
            Ash.Changeset.change_attribute(changeset, key, value)
          end)
        end
      end
    end

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

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

      ash do
        default_domain(Domain)
      end

      input :post

      destroy :delete_post, Post, :destroy do
        initial(input(:post))
      end
    end

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

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

  test "it can destroy and return a post" do
    defmodule ReturningDestroyPostReactor do
      @moduledoc false
      use Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      input :post

      destroy :delete_post, Post, :destroy do
        initial(input(:post))
        return_destroyed?(true)
      end
    end

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

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

    assert original_post.__struct__ == post.__struct__
    assert original_post.id == post.id
    assert post.__meta__.state == :deleted
  end

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

      ash do
        default_domain(Domain)
      end

      input :post

      destroy :delete_post, Post, :destroy do
        initial(input(:post))

        undo :always
        undo_action(:undo_destroy)
      end

      step :fail do
        wait_for :delete_post

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

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

    assert {:error, _} =
             Reactor.run(
               UndoingDestroyPostReactor,
               %{post: post},
               %{},
               async?: false
             )

    assert Post.get(post.id)
  end
end
