defmodule CodeCorps.Accounts.Changesets do
  @moduledoc ~S"""
  Changesets for Code Corps accounts.
  """

  import CodeCorpsWeb.Gettext

  alias CodeCorps.GitHub.Adapters
  alias CodeCorps.Helpers.RandomIconColor
  alias Ecto.Changeset

  @doc ~S"""
  Casts a changeset used for creating a user account from a GitHub user payload
  """
  @spec create_from_github_changeset(struct, map) :: Changeset.t
  def create_from_github_changeset(struct, %{} = params) do
    params =
      params
      |> Adapters.User.to_user()
      |> Enum.reject(fn {_, v} -> is_nil(v) end)
      |> Map.new()

    struct
    |> Changeset.change(params)
    |> Changeset.put_change(:sign_up_context, "github")
    |> Changeset.validate_inclusion(:type, ["bot", "user"])
    |> RandomIconColor.generate_icon_color(:default_color)
    |> Changeset.unique_constraint(:email)
    |> Changeset.assoc_constraint(:github_user)
    |> unique_github_constraint()
  end

  @doc ~S"""
  Casts a changeset used for updating a user account from a GitHub user payload
  """
  @spec update_with_github_user_changeset(struct, map) :: Changeset.t
  def update_with_github_user_changeset(struct, %{} = params) do
    struct
    |> Changeset.cast(params, [:github_avatar_url, :github_id, :github_username, :type])
    |> ensure_email_without_overwriting(params)
    |> Changeset.validate_required([:github_avatar_url, :github_id, :github_username, :type])
    |> Changeset.unique_constraint(:email)
    |> unique_github_constraint()
  end

  @doc ~S"""
  Casts a changeset used for updating a user account from a GitHub OAuth payload
  """
  @spec update_from_github_oauth_changeset(struct, map) :: Changeset.t
  def update_from_github_oauth_changeset(struct, %{} = params) do
    struct
    |> Changeset.cast(params, [:github_auth_token, :github_avatar_url, :github_id, :github_username, :type])
    |> ensure_email_without_overwriting(params)
    |> Changeset.validate_required([:github_auth_token, :github_avatar_url, :github_id, :github_username, :type])
    |> Changeset.unique_constraint(:email)
    |> unique_github_constraint()
  end

  def dissociate_github_user_changeset(struct, %{} = params) do
    struct
    |> Changeset.cast(params, [:github_id, :github_id_was])
    |> Changeset.unique_constraint(:github_id_was)
    |> unique_github_constraint()
  end

  @spec ensure_email_without_overwriting(Changeset.t, map) :: Changeset.t
  defp ensure_email_without_overwriting(%Changeset{} = changeset, %{"email" => new_email} = _params) do
    case changeset |> Changeset.get_field(:email) do
      nil -> changeset |> Changeset.put_change(:email, new_email)
      _email -> changeset
    end
  end
  defp ensure_email_without_overwriting(%Changeset{} = changeset, _params), do: changeset

  defp unique_github_constraint(struct) do
    struct
    |> Changeset.unique_constraint(:github_id, message: dgettext("errors", "account is already connected to someone else"))
  end
end
