defmodule Ash.Test.Resource.Relationships.BelongsToTest do
  @moduledoc false
  use ExUnit.Case, async: true

  alias Ash.Resource.Relationships.BelongsTo
  alias Ash.Test.Domain, as: Domain

  defmacrop defposts(do: body) do
    module = Module.concat(["rand#{System.unique_integer([:positive])}", Post])

    quote do
      defmodule unquote(module) do
        @moduledoc false
        use Ash.Resource, domain: Domain

        attributes do
          uuid_primary_key :id
        end

        unquote(body)
      end

      alias unquote(module), as: Post
    end
  end

  describe "representation" do
    test "it creates an attribute" do
      defposts do
        relationships do
          belongs_to :foobar, FooBar
        end
      end

      assert [
               _,
               %Ash.Resource.Attribute{
                 name: :foobar_id,
                 primary_key?: false,
                 type: Ash.Type.UUID,
                 public?: false
               }
             ] = Ash.Resource.Info.attributes(Post)
    end

    test "it creates an attribute that honors attribute_writable?" do
      defposts do
        relationships do
          belongs_to :foobar, FooBar, attribute_writable?: true, public?: true
        end
      end

      assert [
               _,
               %Ash.Resource.Attribute{
                 name: :foobar_id,
                 primary_key?: false,
                 type: Ash.Type.UUID,
                 public?: true,
                 writable?: true
               }
             ] = Ash.Resource.Info.attributes(Post)
    end

    test "it creates a relationship" do
      defposts do
        relationships do
          belongs_to :foo, Foo, public?: true
          belongs_to :bar, Bar, source_attribute: :bazz
          belongs_to :sorted, Bazzinga, default_sort: [inserted_at: :desc]
        end
      end

      assert [
               %BelongsTo{
                 cardinality: :one,
                 define_attribute?: true,
                 destination: Foo,
                 destination_attribute: :id,
                 attribute_type: :uuid,
                 name: :foo,
                 primary_key?: false,
                 source_attribute: :foo_id,
                 type: :belongs_to,
                 public?: true
               },
               %BelongsTo{
                 cardinality: :one,
                 define_attribute?: true,
                 destination: Bar,
                 destination_attribute: :id,
                 attribute_type: :uuid,
                 name: :bar,
                 primary_key?: false,
                 source_attribute: :bazz,
                 type: :belongs_to,
                 public?: false
               },
               %BelongsTo{
                 cardinality: :one,
                 destination: Bazzinga,
                 destination_attribute: :id,
                 name: :sorted,
                 source_attribute: :sorted_id,
                 type: :belongs_to,
                 public?: false,
                 default_sort: [inserted_at: :desc]
               }
             ] = Ash.Resource.Info.relationships(Post)

      assert [%BelongsTo{name: :foo}] = Ash.Resource.Info.public_relationships(Post)

      assert %BelongsTo{name: :foo} = Ash.Resource.Info.public_relationship(Post, :foo)

      assert nil == Ash.Resource.Info.relationship(Post, :definitely_legit_relationship)

      assert nil == Ash.Resource.Info.public_relationship(Post, :bar)
    end
  end

  describe "validations" do
    test "fails if destination_attribute is not an atom" do
      assert_raise(
        Spark.Error.DslError,
        ~r/invalid value for :destination_attribute option: expected atom, got: \"foo\"/,
        fn ->
          defposts do
            relationships do
              belongs_to(:foobar, FooBar, destination_attribute: "foo", public?: true)
            end
          end
        end
      )
    end

    test "fails if source_attribute is not an atom" do
      assert_raise(
        Spark.Error.DslError,
        ~r/invalid value for :source_attribute option: expected atom, got: \"foo\"/,
        fn ->
          defposts do
            relationships do
              belongs_to(:foobar, FooBar, source_attribute: "foo", public?: true)
            end
          end
        end
      )
    end

    test "fails if the destination is not an atom" do
      assert_raise(
        Spark.Error.DslError,
        ~r/expected module in :destination option, got: \"foobar\"/,
        fn ->
          defposts do
            relationships do
              belongs_to(:foobar, "foobar", public?: true)
            end
          end
        end
      )
    end

    test "fails if the relationship name is not an atom" do
      assert_raise(
        Spark.Error.DslError,
        ~r/invalid value for :name option: expected atom, got: \"foobar\"/,
        fn ->
          defposts do
            relationships do
              belongs_to("foobar", Foobar, public?: true)
            end
          end
        end
      )
    end

    test "fails if `primary_key?` is not a boolean" do
      assert_raise(
        Spark.Error.DslError,
        ~r/invalid value for :primary_key\? option: expected boolean, got: \"blah\"/,
        fn ->
          defposts do
            relationships do
              belongs_to(:foobar, Foobar, primary_key?: "blah", public?: true)
            end
          end
        end
      )
    end

    test "fails if `public?` is not a boolean" do
      assert_raise(
        Spark.Error.DslError,
        ~r/invalid value for :public\? option: expected boolean, got: "blah"/,
        fn ->
          defposts do
            relationships do
              belongs_to(:foobar, Foobar, public?: "blah")
            end
          end
        end
      )
    end
  end

  test "fails if `define_attribute?` is not a boolean" do
    assert_raise(
      Spark.Error.DslError,
      ~r/invalid value for :define_attribute\? option: expected boolean, got: \"blah\"/,
      fn ->
        defposts do
          relationships do
            belongs_to(:foobar, Foobar, define_attribute?: "blah", public?: true)
          end
        end
      end
    )
  end

  test "fails in domain initialization if the destination resource doesn't have the correct field" do
    output =
      ExUnit.CaptureIO.capture_io(:stderr, fn ->
        defposts do
          relationships do
            belongs_to(:post, __MODULE__, define_attribute?: false, public?: true)
          end
        end

        defmodule Domain do
          use Ash.Domain

          resources do
            resource Post
          end
        end
      end)

    assert String.contains?(output, "Relationship") and
             String.contains?(output, "expects source attribute") and
             String.contains?(output, "post_id")
  end
end
