defmodule Ash.Test.Reactor.TransactionTest do
  @moduledoc false
  use ExUnit.Case, async: false
  use Mimic

  alias Ash.Test.Domain

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

    attributes do
      uuid_primary_key :id
      attribute :title, :string, allow_nil?: false, public?: true
    end

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

  setup do
    ExUnit.CaptureLog.capture_log(fn ->
      Ash.DataLayer.Mnesia.start(Domain, [Post])
    end)

    on_exit(fn ->
      ExUnit.CaptureLog.capture_log(fn ->
        :mnesia.stop()
        :mnesia.delete_schema([node()])
      end)
    end)
  end

  test "when the transaction completes successfully it returns the last result" do
    defmodule SuccessfulNoReturnTransactionReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain(Domain)
      end

      transaction :create_posts, Post do
        create :post_1, Post, :create do
          inputs(%{title: value("About Marty McFly")})
        end

        create :post_2, Post, :create do
          inputs(%{title: value("About Doc Brown")})
        end
      end
    end

    assert {:ok, %{title: "About Doc Brown"}} = Reactor.run(SuccessfulNoReturnTransactionReactor)
  end

  test "when the transaction completes successfully it returns the specified result" do
    defmodule SuccessfulNamedReturnTransactionReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain(Domain)
      end

      transaction :create_posts, Post do
        create :post_1, Post, :create do
          inputs(%{title: value("About Marty McFly")})
        end

        create :post_2, Post, :create do
          inputs(%{title: value("About Doc Brown")})
        end

        return :post_1
      end
    end

    assert %{title: "About Marty McFly"} =
             Reactor.run!(SuccessfulNamedReturnTransactionReactor)
  end

  test "when the transaction fails it is rolled back" do
    defmodule FailAndRollBackTransactionReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain(Domain)
      end

      transaction :create_posts, Post do
        create :post_1, Post, :create do
          inputs(%{title: value("About Marty McFly")})
        end

        step :fail do
          run fn _, _ ->
            raise "hell"
          end
        end

        return :post_1
      end
    end

    Ash.DataLayer
    |> expect(:rollback, fn resources, reason ->
      assert resources == [Post]
      assert Exception.message(reason) == "hell"

      raise reason
    end)

    FailAndRollBackTransactionReactor
    |> Reactor.run(%{}, %{}, async?: false)
    |> Ash.Test.assert_has_error(fn
      %Reactor.Error.Invalid.RunStepError{error: error} ->
        Exception.message(error) =~ "hell"

      _ ->
        false
    end)
  end

  test "steps inside a transaction can refer to step results outside the transaction" do
    defmodule OutsideResultReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain Domain
      end

      step :title do
        run fn _ ->
          {:ok, "About Einstein the dog"}
        end
      end

      transaction :create_post, Post do
        create :post, Post, :create do
          inputs(%{title: result(:title)})
        end

        return :post
      end
    end

    assert %{title: "About Einstein the dog"} = Reactor.run!(OutsideResultReactor)
  end

  test "steps inside a transaction can refer to inputs outside the transaction" do
    defmodule OutsideInputReactor do
      @moduledoc false
      use Ash.Reactor

      ash do
        default_domain Domain
      end

      input :title

      transaction :create_post, Post do
        create :post, Post, :create do
          inputs(%{title: input(:title)})
        end

        return :post
      end
    end

    assert %{title: "About Einstein the dog"} =
             Reactor.run!(OutsideInputReactor, %{title: "About Einstein the dog"})
  end
end
