defmodule Mv.Accounts.User.Validations.EmailNotUsedByOtherMember do
  @moduledoc """
  Validates that the user's email is not already used by another member.
  Only validates when:
  - User is already linked to a member (member_id != nil) AND email is changing
  - User is being linked to a member (member relationship is changing)

  This allows creating users with the same email as unlinked members.
  """
  use Ash.Resource.Validation

  require Logger

  @doc """
  Validates email uniqueness across linked User-Member pairs.

  This validation ensures that when a user is linked to a member, their email
  does not conflict with another member's email. It only runs when necessary
  to avoid blocking valid operations (see `@moduledoc` for trigger conditions).

  ## Parameters
  - `changeset` - The Ash changeset being validated
  - `_opts` - Options passed to the validation (unused)
  - `_context` - Ash context map (unused)

  ## Returns
  - `:ok` if validation passes or should be skipped
  - `{:error, field: :email, message: ..., value: ...}` if validation fails
  """
  @impl true
  def validate(changeset, _opts, _context) do
    email_changing? = Ash.Changeset.changing_attribute?(changeset, :email)
    member_changing? = Ash.Changeset.changing_relationship?(changeset, :member)

    member_id = Ash.Changeset.get_attribute(changeset, :member_id)
    is_linked? = not is_nil(member_id)

    # Only validate if:
    # 1. User is linked AND email is changing
    # 2. User is being linked/unlinked (member relationship changing)
    should_validate? = (is_linked? and email_changing?) or member_changing?

    if should_validate? do
      case Ash.Changeset.fetch_change(changeset, :email) do
        {:ok, new_email} ->
          # Extract member_id from relationship changes for new links
          member_id_to_exclude = get_member_id_from_changeset(changeset)
          check_email_uniqueness(new_email, member_id_to_exclude)

        :error ->
          # No email change, get current email
          current_email = Ash.Changeset.get_attribute(changeset, :email)
          # Extract member_id from relationship changes for new links
          member_id_to_exclude = get_member_id_from_changeset(changeset)
          check_email_uniqueness(current_email, member_id_to_exclude)
      end
    else
      :ok
    end
  end

  # Extract member_id from changeset, checking relationship changes first
  # This is crucial for new links where member_id is in manage_relationship changes
  defp get_member_id_from_changeset(changeset) do
    # Try to get from relationships (for new links via manage_relationship)
    case Map.get(changeset.relationships, :member) do
      [{[%{id: id}], _opts}] when not is_nil(id) ->
        # Found in relationships - this is a new link
        id

      _ ->
        # Fall back to attribute (for existing links)
        Ash.Changeset.get_attribute(changeset, :member_id)
    end
  end

  defp check_email_uniqueness(email, exclude_member_id) do
    alias Mv.Helpers
    alias Mv.Helpers.SystemActor

    query =
      Mv.Membership.Member
      |> Ash.Query.filter(email == ^to_string(email))
      |> Mv.Helpers.query_exclude_id(exclude_member_id)

    system_actor = SystemActor.get_system_actor()
    opts = Helpers.ash_actor_opts(system_actor)

    case Ash.read(query, opts) do
      {:ok, []} ->
        :ok

      {:ok, _} ->
        {:error, field: :email, message: "is already used by another member", value: email}

      {:error, reason} ->
        Logger.warning(
          "Email uniqueness validation query failed for user email '#{email}': #{inspect(reason)}. Allowing operation to proceed (fail-open)."
        )

        :ok
    end
  end
end
