defmodule Mv.Authorization.Checks.CustomFieldValueCreateScope do
  @moduledoc """
  Policy check for CustomFieldValue create actions only.

  Use this for create instead of HasPermission because Ash cannot apply
  filters to create actions ("Cannot use a filter to authorize a create").
  This check performs the same scope logic as HasPermission for create
  (PermissionSets + :linked/:all) but only implements strict_check, so it
  never adds a filter.

  ## member_id source

  The check reads `member_id` from the create changeset via
  `Ash.Changeset.get_argument_or_attribute/2`, so it works when member_id
  is set as an attribute or as an action argument. The CustomFieldValue
  resource's default create action must accept and require `member_id`
  (e.g. via `default_accept [:value, :member_id, :custom_field_id]`).

  Used in CustomFieldValue policies:
    policy action_type(:create) do
      authorize_if Mv.Authorization.Checks.CustomFieldValueCreateScope
    end
  """
  use Ash.Policy.Check
  alias Mv.Authorization.PermissionSets

  @impl true
  def describe(_opts),
    do: "CustomFieldValue create allowed by permission set scope (:linked or :all)"

  @impl true
  def strict_check(actor, authorizer, _opts) do
    actor = ensure_role_loaded(actor)

    with %{role: %{permission_set_name: ps_name}} when not is_nil(ps_name) <- actor,
         {:ok, ps_atom} <- PermissionSets.permission_set_name_to_atom(ps_name),
         permissions <- PermissionSets.get_permissions(ps_atom),
         perm <- find_custom_field_value_create(permissions.resources) do
      case perm do
        nil -> {:ok, false}
        %{scope: :all} -> {:ok, true}
        %{scope: :linked} -> {:ok, member_id_matches?(authorizer, actor)}
      end
    else
      _ -> {:ok, false}
    end
  end

  defp find_custom_field_value_create(resources) do
    Enum.find(resources, fn p ->
      p.resource == "CustomFieldValue" and p.action == :create and p.granted
    end)
  end

  defp member_id_matches?(authorizer, actor) do
    member_id = get_create_member_id(authorizer)
    !is_nil(member_id) and member_id == actor.member_id
  end

  defp get_create_member_id(authorizer) do
    changeset = authorizer.changeset || authorizer.subject

    if changeset && function_exported?(Ash.Changeset, :get_argument_or_attribute, 2) do
      Ash.Changeset.get_argument_or_attribute(changeset, :member_id)
    else
      nil
    end
  end

  defp ensure_role_loaded(actor), do: Mv.Authorization.Actor.ensure_loaded(actor)
end
