defmodule WhoNeedHelpWeb.SocialOAuthController do
  use WhoNeedHelpWeb, :controller

  require Logger

  alias WhoNeedHelp.{Accounts, Repo, SocialOAuth, Trust}

  def request(conn, %{"provider" => provider_param}) do
    user = conn.assigns.current_scope.user

    with {:ok, provider} <- SocialOAuth.provider(provider_param),
         true <- SocialOAuth.enabled?(provider),
         redirect_uri <- callback_url(provider),
         {:ok, %{url: url, session_params: session_params}} <-
           SocialOAuth.authorize_url(provider, redirect_uri) do
      conn
      |> put_session(session_key(provider), %{
        "user_id" => user.id,
        "session_params" => session_params
      })
      |> redirect(external: url)
    else
      false ->
        oauth_error(conn, gettext("That verification provider is not configured."))

      {:error, :unsupported_provider} ->
        oauth_error(conn, gettext("That verification provider is not supported."))

      {:error, error} ->
        Logger.warning("Unable to start social OAuth (#{error_name(error)})")
        oauth_error(conn, gettext("Social verification could not be started."))
    end
  end

  def callback(conn, %{"provider" => provider_param} = params) do
    user = conn.assigns.current_scope.user

    with {:ok, provider} <- SocialOAuth.provider(provider_param),
         %{"user_id" => user_id, "session_params" => session_params} <-
           get_session(conn, session_key(provider)),
         true <- user_id == user.id,
         redirect_uri <- callback_url(provider),
         {:ok, identity_attrs} <-
           SocialOAuth.callback(provider, redirect_uri, params, session_params),
         {:ok, _identity} <-
           Repo.transact(fn ->
             with {:ok, identity} <-
                    Accounts.upsert_verified_social_identity(user, identity_attrs),
                  {:ok, _audit} <-
                    Trust.audit(
                      user.id,
                      "social_identity.verified",
                      "social_identity",
                      identity.id,
                      %{"provider" => to_string(identity.provider)}
                    ) do
               {:ok, identity}
             end
           end) do
      conn
      |> delete_session(session_key(provider))
      |> put_flash(:info, gettext("GitHub profile verified."))
      |> redirect(to: ~p"/profile")
    else
      nil ->
        callback_error(
          conn,
          provider_param,
          gettext("Social verification session expired. Please retry.")
        )

      false ->
        callback_error(
          conn,
          provider_param,
          gettext("Social verification session is invalid.")
        )

      {:error, :already_linked} ->
        callback_error(conn, provider_param, gettext("That social account is already linked."))

      {:error, error} ->
        Logger.warning("Social OAuth callback failed (#{error_name(error)})")

        callback_error(
          conn,
          provider_param,
          gettext("Social verification failed. Please retry.")
        )
    end
  end

  defp callback_url(provider) do
    url(~p"/auth/social/#{provider}/callback")
  end

  defp session_key(provider), do: "social_oauth_#{provider}"

  defp callback_error(conn, provider, message) do
    conn
    |> delete_session("social_oauth_#{provider}")
    |> oauth_error(message)
  end

  defp oauth_error(conn, message) do
    conn
    |> put_flash(:error, message)
    |> redirect(to: ~p"/profile")
  end

  defp error_name(error) when is_atom(error), do: Atom.to_string(error)
  defp error_name(%{__struct__: module}), do: inspect(module)
  defp error_name(_error), do: "unknown_error"
end
