defmodule Mv.EmailSync.Changes.SyncUserEmailToMember do
  @moduledoc """
  Synchronizes User.email → Member.email
  User.email is always the source of truth.

  Trigger conditions are configured in resources via `where` clauses:
  - User resource: Use `where: [changing(:email)]` or `where: any([changing(:email), changing(:member)])`
  - Member resource: Use `where: [changing(:user)]`

  Can be used by both User and Member resources.
  """
  use Ash.Resource.Change
  alias Mv.EmailSync.{Helpers, Loader}

  @doc """
  Implements the email synchronization from User to Member.

  This function is called automatically by Ash when the configured trigger
  conditions are met (see `@moduledoc` for trigger details).

  ## Parameters
  - `changeset` - The Ash changeset being processed
  - `_opts` - Options passed to the change (unused)
  - `context` - Ash context map containing metadata (e.g., `:syncing_email` flag)

  ## Returns
  Modified changeset with email synchronization applied, or original changeset
  if recursion detected.
  """
  # Ash 3.12+ calls this to decide whether to run the change in certain contexts.
  @impl true
  def has_change?, do: true

  @impl true
  def change(changeset, _opts, context) do
    # Only recursion protection needed - trigger logic is in `where` clauses
    if Map.get(context, :syncing_email, false) do
      changeset
    else
      sync_email(changeset)
    end
  end

  defp sync_email(changeset) do
    Ash.Changeset.around_transaction(changeset, fn cs, callback ->
      result = callback.(cs)
      apply_sync(result)
    end)
  end

  defp apply_sync(result) do
    with {:ok, record} <- Helpers.extract_record(result),
         {:ok, user, member} <- get_user_and_member(record) do
      sync_by_record_type(result, record, user, member)
    else
      _ -> result
    end
  end

  # When called from Member-side, we update the member in the result.
  # When called from User-side, we sync user email to the linked member in DB.
  defp sync_by_record_type(result, %Mv.Membership.Member{}, user, _member) do
    Helpers.override_with_linked_email(result, user.email)
  end

  defp sync_by_record_type(result, %Mv.Accounts.User{}, user, member) do
    Helpers.sync_email_to_linked_record(result, member, user.email)
  end

  # Retrieves user and member - works for both resource types
  # Uses system actor via Loader functions
  defp get_user_and_member(%Mv.Accounts.User{} = user) do
    case Loader.get_linked_member(user) do
      nil -> {:error, :no_member}
      member -> {:ok, user, member}
    end
  end

  defp get_user_and_member(%Mv.Membership.Member{} = member) do
    case Loader.load_linked_user!(member) do
      {:ok, user} -> {:ok, user, member}
      error -> error
    end
  end
end
