defmodule Mv.Vereinfacht.Changes.SyncContact do
  @moduledoc """
  Syncs a member to Vereinfacht as a finance contact after create/update.

  - If the member has no `vereinfacht_contact_id`, creates a contact via API and saves the ID.
  - If the member already has an ID, updates the contact via API.
  Runs in `after_transaction` so the member is persisted first. API failures are logged
  but do not block the member operation. Requires Vereinfacht to be configured
  (Mv.Config.vereinfacht_configured?/0).

  Only runs when relevant data changed: on create always; on update only when
  first_name, last_name, email, street, house_number, postal_code, or city changed,
  or when the member has no vereinfacht_contact_id yet (to avoid unnecessary API calls).
  """
  use Ash.Resource.Change

  require Logger

  @synced_attributes [
    :first_name,
    :last_name,
    :email,
    :street,
    :house_number,
    :postal_code,
    :city
  ]

  @impl true
  def change(changeset, _opts, _context) do
    if Mv.Config.vereinfacht_configured?() and sync_relevant?(changeset) do
      Ash.Changeset.after_transaction(changeset, &sync_after_transaction/2)
    else
      changeset
    end
  end

  defp sync_relevant?(changeset) do
    case changeset.action_type do
      :create -> true
      :update -> relevant_update?(changeset)
      _ -> false
    end
  end

  defp relevant_update?(changeset) do
    any_synced_attr_changed? =
      Enum.any?(@synced_attributes, &Ash.Changeset.changing_attribute?(changeset, &1))

    record = changeset.data
    no_contact_id_yet? = record && blank_contact_id?(record.vereinfacht_contact_id)

    any_synced_attr_changed? or no_contact_id_yet?
  end

  defp blank_contact_id?(nil), do: true
  defp blank_contact_id?(""), do: true
  defp blank_contact_id?(s) when is_binary(s), do: String.trim(s) == ""
  defp blank_contact_id?(_), do: false

  # Ash calls after_transaction with (changeset, result) only - 2 args.
  defp sync_after_transaction(_changeset, {:ok, member}) do
    case Mv.Vereinfacht.sync_member(member) do
      :ok ->
        Mv.Vereinfacht.SyncFlash.store(to_string(member.id), :ok, "Synced to Vereinfacht.")
        {:ok, member}

      {:ok, member_updated} ->
        Mv.Vereinfacht.SyncFlash.store(
          to_string(member_updated.id),
          :ok,
          "Synced to Vereinfacht."
        )

        {:ok, member_updated}

      {:error, reason} ->
        Logger.warning("Vereinfacht sync failed for member #{member.id}: #{inspect(reason)}")

        Mv.Vereinfacht.SyncFlash.store(
          to_string(member.id),
          :warning,
          Mv.Vereinfacht.format_error(reason)
        )

        {:ok, member}
    end
  end

  defp sync_after_transaction(_changeset, error), do: error
end
