defmodule Mv.EmailSync.Helpers do
  @moduledoc """
  Shared helper functions for email synchronization between User and Member.

  Handles the complexity of `around_transaction` callback results and 
  provides clean abstractions for email updates within transactions.
  """

  require Logger
  import Ecto.Changeset

  @doc """
  Extracts the record from an Ash action result.

  Handles both 2-tuple `{:ok, record}` and 4-tuple 
  `{:ok, record, changeset, notifications}` patterns.
  """
  def extract_record({:ok, record, _changeset, _notifications}), do: {:ok, record}
  def extract_record({:ok, record}), do: {:ok, record}
  def extract_record({:error, _} = error), do: error

  @doc """
  Updates the result with a new record while preserving the original structure.

  If the original result was a 4-tuple, returns a 4-tuple with the updated record.
  If it was a 2-tuple, returns a 2-tuple with the updated record.
  """
  def update_result_record({:ok, _old_record, changeset, notifications}, new_record) do
    {:ok, new_record, changeset, notifications}
  end

  def update_result_record({:ok, _old_record}, new_record) do
    {:ok, new_record}
  end

  @doc """
  Updates an email field directly via Ecto within the current transaction.

  This bypasses Ash's action system to ensure the update happens in the
  same database transaction as the parent action.
  """
  def update_email_via_ecto(record, new_email) do
    record
    |> cast(%{email: to_string(new_email)}, [:email])
    |> Mv.Repo.update()
  end

  @doc """
  Synchronizes email to a linked record if it exists.

  Returns the original result unchanged, or an error if sync fails.
  """
  def sync_email_to_linked_record(result, linked_record, new_email) do
    with {:ok, _source} <- extract_record(result),
         record when not is_nil(record) <- linked_record,
         {:ok, _updated} <- update_email_via_ecto(record, new_email) do
      # Successfully synced - return original result unchanged
      result
    else
      nil ->
        # No linked record - return original result
        result

      {:error, error} ->
        # Sync failed - log and propagate error to rollback transaction
        Logger.error("Email sync failed: #{inspect(error)}")
        {:error, error}
    end
  end

  @doc """
  Overrides the record's email with the linked email if emails differ.

  Returns updated result with new record, or original result if no update needed.
  """
  def override_with_linked_email(result, linked_email) do
    with {:ok, record} <- extract_record(result),
         true <- record.email != to_string(linked_email),
         {:ok, updated_record} <- update_email_via_ecto(record, linked_email) do
      # Email was different - return result with updated record
      update_result_record(result, updated_record)
    else
      false ->
        # Emails already match - no update needed
        result

      {:error, error} ->
        # Override failed - log and propagate error
        Logger.error("Email override failed: #{inspect(error)}")
        {:error, error}
    end
  end
end
