defmodule WhoNeedHelpWeb.UserRegistrationController do
  use WhoNeedHelpWeb, :controller

  require Logger

  alias WhoNeedHelp.{Accounts, GoogleAuth}
  alias WhoNeedHelp.Accounts.User
  alias WhoNeedHelp.Trust.RateLimiter

  plug :put_no_store

  def new(conn, _params) do
    changeset = Accounts.change_user_registration(%User{}, %{}, validate_unique: false)
    render_registration(conn, changeset)
  end

  def create(conn, %{"user" => user_params}) when is_map(user_params) do
    user_params = normalize_registration_params(user_params)
    email_scope = normalize_email_scope(user_params["email"])

    with {:ok, _limit} <- RateLimiter.check(:registration_email, email_scope),
         result <- Accounts.register_user(user_params) do
      case result do
        {:ok, user} ->
          deliver_registration_instructions(conn, user)
          registration_response(conn)

        {:error, %Ecto.Changeset{} = changeset} ->
          if unique_email_error?(changeset) do
            case Accounts.get_user_by_email(user_params["email"]) do
              %User{moderation_status: status} = user when status != :suspended ->
                deliver_registration_instructions(conn, user)

              _missing_or_suspended ->
                :ok
            end

            registration_response(conn)
          else
            render_registration(conn, changeset)
          end
      end
    else
      {:error, :rate_limited} ->
        conn
        |> put_status(:too_many_requests)
        |> put_flash(
          :error,
          gettext("Too many registration attempts in the configured time window.")
        )
        |> render_registration(Accounts.change_user_registration(%User{}, user_params))
    end
  end

  def create(conn, _params) do
    conn
    |> put_status(:bad_request)
    |> put_flash(:error, gettext("The registration form is invalid."))
    |> render_registration(
      Accounts.change_user_registration(%User{}, %{}, validate_unique: false)
    )
  end

  defp normalize_email_scope(email) when is_binary(email),
    do: email |> String.trim() |> String.downcase()

  defp normalize_email_scope(_email), do: ""

  defp normalize_registration_params(params) do
    params
    |> normalize_binary_param("email")
    |> normalize_binary_param("display_name")
    |> normalize_binary_param("locale")
    |> normalize_boolean_param("terms_accepted")
  end

  defp normalize_binary_param(params, key) do
    case Map.fetch(params, key) do
      {:ok, value} when is_binary(value) -> params
      {:ok, _invalid} -> Map.put(params, key, "")
      :error -> params
    end
  end

  defp normalize_boolean_param(params, key) do
    case Map.fetch(params, key) do
      {:ok, value} when is_binary(value) or is_boolean(value) -> params
      {:ok, _invalid} -> Map.put(params, key, false)
      :error -> params
    end
  end

  defp deliver_registration_instructions(conn, user) do
    case Accounts.deliver_login_instructions(
           user,
           &"#{url(conn, ~p"/users/log-in")}#token=#{URI.encode_www_form(&1)}"
         ) do
      {:ok, _email} -> :ok
      {:error, _reason} -> Logger.warning("Unable to send registration login instructions")
    end
  end

  defp registration_response(conn) do
    conn
    |> put_flash(
      :info,
      gettext(
        "If this address can be registered or signed in, login instructions will arrive shortly."
      )
    )
    |> redirect(to: ~p"/users/log-in")
  end

  defp unique_email_error?(%Ecto.Changeset{} = changeset) do
    Enum.any?(changeset.errors, fn
      {:email, {_message, options}} ->
        Keyword.get(options, :constraint) == :unique or
          Keyword.get(options, :validation) == :unsafe_unique

      _other ->
        false
    end)
  end

  defp render_registration(conn, changeset) do
    render(conn, :new,
      changeset: changeset,
      google_auth_enabled: GoogleAuth.enabled?()
    )
  end

  defp put_no_store(conn, _opts), do: put_resp_header(conn, "cache-control", "no-store")
end
