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

  alias Ash.Test.Domain, as: Domain

  defmodule Name do
    use Ash.Resource.Calculation

    @impl true
    def load(_, _, _), do: [:first_name, :last_name, :show_last_name]

    @impl true
    def calculate(records, _, _) do
      {:ok,
       records
       |> Enum.map(fn
         %{
           first_name: first_name,
           show_last_name: true,
           last_name: last_name
         } ->
           "#{first_name} #{last_name}"

         %{
           first_name: first_name,
           show_last_name: false
         } ->
           first_name
       end)}
    end
  end

  defmodule SelfName do
    use Ash.Resource.Calculation

    @impl true
    def load(_, _, _), do: [self: [:first_name, :last_name, :show_last_name]]

    @impl true
    def calculate(records, _, _) do
      {:ok,
       records
       |> Enum.map(fn
         %{
           self: %{
             first_name: first_name,
             show_last_name: true,
             last_name: last_name
           }
         } ->
           "#{first_name} #{last_name}"

         %{
           self: %{
             first_name: first_name,
             show_last_name: false
           }
         } ->
           first_name
       end)}
    end
  end

  defmodule SelfSelfName do
    use Ash.Resource.Calculation

    @impl true
    def load(_, _, _), do: [self: [self: [:first_name, :last_name, :show_last_name]]]

    @impl true
    def calculate(records, _, _) do
      {:ok,
       records
       |> Enum.map(fn
         %{
           self: %{
             self: %{
               first_name: first_name,
               show_last_name: true,
               last_name: last_name
             }
           }
         } ->
           "#{first_name} #{last_name}"

         %{
           self: %{
             self: %{
               first_name: first_name,
               show_last_name: false
             }
           }
         } ->
           first_name
       end)}
    end
  end

  defmodule EmbeddedNotAllowed do
    use Ash.Resource.Calculation

    @impl true
    def load(_, _, _), do: [embedded: [:not_allowed]]

    @impl true
    def calculate(records, _, _) do
      Enum.map(records, fn record ->
        record.embedded && record.embedded.not_allowed
      end)
    end
  end

  defmodule Embedded do
    use Ash.Resource,
      data_layer: :embedded,
      authorizers: [Ash.Policy.Authorizer]

    resource do
      require_primary_key? false
    end

    attributes do
      attribute :allowed, :string, public?: true
      attribute :not_allowed, :string, public?: true
    end

    policies do
      policy always() do
        authorize_if always()
      end
    end

    field_policies do
      field_policy :allowed do
        authorize_if always()
      end

      field_policy :not_allowed do
        forbid_if always()
      end
    end
  end

  defmodule Resource do
    use Ash.Resource,
      domain: Domain,
      authorizers: [Ash.Policy.Authorizer],
      data_layer: Ash.DataLayer.Ets

    ets do
      private? true
    end

    attributes do
      uuid_primary_key :id

      attribute :first_name, :string
      attribute :last_name, :string

      attribute :show_last_name, :boolean, public?: true
      attribute :embedded, Embedded
    end

    code_interface do
      domain Domain

      define :create
      define :read
    end

    calculations do
      calculate :name, :string, Name, public?: true
      calculate :self_name, :string, SelfName, public?: true
      calculate :self_self_name, :string, SelfSelfName, public?: true
      calculate :embedded_not_allowed, :string, EmbeddedNotAllowed, public?: true
    end

    policies do
      policy always() do
        authorize_if always()
      end
    end

    field_policies do
      field_policy :show_last_name do
        forbid_if always()
      end

      field_policy [:name, :self_name, :self_self_name, :embedded_not_allowed] do
        authorize_if always()
      end
    end

    actions do
      defaults [:read, create: [:first_name, :last_name, :show_last_name, :embedded]]
    end

    relationships do
      has_one :self, __MODULE__ do
        source_attribute :id
        destination_attribute :id
      end
    end
  end

  test "can calculate even if calculate depends on forbidden field" do
    Resource.create!(%{first_name: "Homer", last_name: "Simpson", show_last_name: false})

    [%{name: "Homer", show_last_name: %Ash.ForbiddenField{}}] =
      Resource.read!(%{}, load: [:name, :show_last_name], authorize?: true)
  end

  test "can calculate even if calculate depends on forbidden field from a relationship" do
    Resource.create!(%{first_name: "Homer", last_name: "Simpson", show_last_name: false})

    [%{self_name: "Homer"}] =
      Resource.read!(%{}, load: [:self_name, self: [:show_last_name]], authorize?: true)
  end

  test "can calculate even if calculate depends on forbidden field from a relationsip, and the field will show as forbidden in the final data" do
    Resource.create!(%{first_name: "Homer", last_name: "Simpson", show_last_name: false})

    [%{self_name: "Homer", self: %{show_last_name: %Ash.ForbiddenField{}}}] =
      Resource.read!(%{}, load: [:self_name, self: [:show_last_name]], authorize?: true)
  end

  test "can calculate even if calculate depends on forbidden field from a relationsip two levels deep, and the field will show as forbidden in the final data" do
    Resource.create!(%{first_name: "Homer", last_name: "Simpson", show_last_name: false})

    [%{self_self_name: "Homer", self: %{self: %{show_last_name: %Ash.ForbiddenField{}}}}] =
      Resource.read!(%{},
        load: [:self_self_name, self: [self: [:show_last_name]]],
        authorize?: true
      )
  end

  test "can calculate even if calculate depends on forbidden field from an embedded attribute" do
    Resource.create!(%{
      first_name: "Homer",
      last_name: "Simpson",
      show_last_name: false,
      embedded: %{allowed: "shown", not_allowed: "hidden"}
    })

    [%{embedded_not_allowed: "hidden"}] =
      Resource.read!(%{}, load: [:embedded_not_allowed], authorize?: true)
  end

  test "can calculate even if calculate depends on forbidden field from an embedded field, and the field will show as forbidden in the final data" do
    Resource.create!(%{
      first_name: "Homer",
      last_name: "Simpson",
      show_last_name: false,
      embedded: %{allowed: "shown", not_allowed: "hidden"}
    })

    [%{embedded_not_allowed: "hidden", embedded: %{not_allowed: %Ash.ForbiddenField{}}}] =
      Resource.read!(%{}, load: [:embedded_not_allowed], authorize?: true)
  end
end
