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

  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, data_layer: Ash.DataLayer.Ets

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

        attributes do
          uuid_primary_key :id
        end

        unquote(body)
      end

      alias unquote(module), as: Post
    end
  end

  defmodule Concat do
    # An example concatenation calculation, that accepts the delimiter as an argument
    use Ash.Resource.Calculation

    def init(opts) do
      if opts[:keys] && is_list(opts[:keys]) && Enum.all?(opts[:keys], &is_atom/1) do
        {:ok, opts}
      else
        {:error, "Expected a `keys` option for which keys to concat"}
      end
    end

    def calculate(records, opts, %Ash.Resource.Calculation.Context{
          arguments: %{separator: separator}
        }) do
      Enum.map(records, fn record ->
        Enum.map_join(opts[:keys], separator, fn key ->
          to_string(Map.get(record, key))
        end)
      end)
    end
  end

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

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

    attributes do
      uuid_primary_key :id

      attribute :name, :string do
        public?(true)
      end
    end
  end

  test "it returns the correct error when doing a read with no data layer setup" do
    Post
    |> Ash.Changeset.for_create(:create, %{name: "foo"})
    |> Ash.create()

    {_, error} = Ash.read(Post)
    [%Ash.Error.SimpleDataLayer.NoDataProvided{message: message} | _] = error.errors
    assert message != nil
  end

  test "fails if there are multiple fields that share the same name" do
    assert_raise(
      Spark.Error.DslError,
      ~r/There are 4 fields\(attributes, calculations, aggregates, and relationships\) that share the name `foobar`/,
      fn ->
        defposts do
          attributes do
            attribute(:foobar, :string, public?: true)
          end

          relationships do
            belongs_to :foobar, Foobar do
              public?(true)
            end
          end

          calculations do
            calculate :foobar, :integer, {Concat, keys: [:foo, :bar]} do
              public?(true)
            end
          end

          aggregates do
            count :foobar, :baz do
              public? true
            end
          end
        end
      end
    )
  end

  test "relationships can be loaded" do
    defmodule Leg do
      @moduledoc false
      use Ash.Resource,
        domain: nil,
        data_layer: Ash.DataLayer.Ets,
        validate_domain_inclusion?: false

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

      relationships do
        belongs_to :pants, Ash.Test.Resource.ResourceTest.Pants do
          attribute_writable? true
          public? true
        end
      end

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

    defmodule Pants do
      @moduledoc false
      use Ash.Resource,
        domain: nil,
        data_layer: Ash.DataLayer.Ets,
        validate_domain_inclusion?: false

      attributes do
        uuid_primary_key :id
      end

      relationships do
        has_many :legs, Leg
      end

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

    defmodule Clothing do
      @moduledoc false
      use Ash.Domain, validate_config_inclusion?: false

      resources do
        allow_unregistered? true
      end
    end

    pants =
      Pants
      |> Ash.Changeset.for_create(:create, %{}, domain: Clothing)
      |> Ash.create!(domain: Clothing)

    [left, _right] =
      ~w[left right]
      |> Enum.map(fn side ->
        Leg
        |> Ash.Changeset.for_create(:create, %{pants_id: pants.id, side: side}, domain: Clothing)
        |> Ash.create!(domain: Clothing)
      end)

    Ash.load!(left, [pants: [:legs]], domain: Clothing)
  end

  test "no attributes relationships can be loaded" do
    defmodule TimeMachine do
      @moduledoc false
      use Ash.Resource,
        domain: Ash.Test.Domain,
        data_layer: Ash.DataLayer.Ets,
        validate_domain_inclusion?: false

      attributes do
        uuid_primary_key :id
        attribute :name, :string, public?: true
        attribute :current_year, :integer, public?: true
      end

      relationships do
        has_one :next_machine, __MODULE__ do
          no_attributes? true
          filter expr(current_year > parent(current_year) and id != parent(id))
          sort current_year: :asc
          public? true
          writable? false
          from_many? true
        end
      end

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

    delorean = Ash.create!(TimeMachine, %{name: "Delorean DMC-12", current_year: 1985})
    Ash.create!(TimeMachine, %{name: "San Dimas Phone Booth", current_year: 1988})

    delorean = Ash.load!(delorean, [:next_machine])
    assert delorean.next_machine.name == "San Dimas Phone Booth"

    delorean = Ash.load!(delorean, next_machine: [:name])
    assert delorean.next_machine.name == "San Dimas Phone Booth"
  end
end
