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

  alias __MODULE__, as: Self
  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
    end

    relationships do
      has_many :comments, Self.Comment, public?: true
    end

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

    code_interface do
      define :create
    end
  end

  defmodule Comment 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 :comment, :string, allow_nil?: false, public?: true
    end

    relationships do
      belongs_to :post, Self.Post, public?: true
    end

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

    code_interface do
      define :create
    end
  end

  defmodule SimpleReadOneReactor do
    @moduledoc false
    use Reactor, extensions: [Ash.Reactor]

    ash do
      default_domain(Domain)
    end

    read_one(:read_one_post, Ash.Test.ReactorReadOneTest.Post, :read)
  end

  test "when more than one record is returned it returns an error" do
    ~w[Marty Doc Einstein]
    |> Enum.each(&Post.create!(%{title: &1}))

    SimpleReadOneReactor
    |> Reactor.run(%{}, %{}, async?: false)
    |> Ash.Test.assert_has_error(fn
      %Reactor.Error.Invalid.RunStepError{error: error} ->
        Exception.message(error) =~ "expected at most one result"

      _ ->
        false
    end)
  end

  test "when no records are returned it returns nil" do
    assert {:ok, nil} = Reactor.run(SimpleReadOneReactor, %{}, %{}, async?: false)
  end

  test "when no records are returned it can return an error" do
    defmodule NotFoundReactor do
      @moduledoc false
      use Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      read_one :read_one_post, Ash.Test.ReactorReadOneTest.Post, :read do
        fail_on_not_found?(true)
      end
    end

    NotFoundReactor
    |> Reactor.run(%{}, %{}, async?: false)
    |> Ash.Test.assert_has_error(fn
      %Reactor.Error.Invalid.RunStepError{
        error: %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{}]}
      } ->
        true

      _ ->
        false
    end)
  end

  test "when exactly one record is returned it returns it" do
    expected = Post.create!(%{title: "Marty"})

    assert {:ok, actual} = Reactor.run(SimpleReadOneReactor, %{}, %{}, async?: false)
    assert expected.id == actual.id
  end

  test "it can load related data when asked" do
    defmodule LoadRelatedReactor do
      @moduledoc false
      use Reactor, extensions: [Ash.Reactor]

      ash do
        default_domain(Domain)
      end

      read_one :read_one_post, Ash.Test.ReactorReadOneTest.Post, :read do
        load value(comments: [:comment])
      end
    end

    post = Post.create!(%{title: "Marty"})
    comments = ["This is heavy", "You made a time machine... out of a Delorean?"]

    for comment <- comments do
      Comment.create!(%{post_id: post.id, comment: comment})
    end

    assert {:ok, post} = Reactor.run(LoadRelatedReactor, %{}, %{}, async?: false)
    assert Enum.sort(Enum.map(post.comments, & &1.comment)) == comments
  end
end
