defmodule WhoNeedHelp.GoogleAuth.AssentAdapter do
  @moduledoc false

  @behaviour WhoNeedHelp.GoogleAuth

  alias Assent.Strategy.Google

  @impl true
  def enabled?, do: not is_nil(provider_config())

  @impl true
  def authorize_url(redirect_uri) do
    with {:ok, config} <- config(redirect_uri) do
      config
      |> Keyword.put(:nonce, random_url_token(32))
      |> Keyword.put(:code_verifier, true)
      |> Google.authorize_url()
    end
  end

  @impl true
  def callback(redirect_uri, params, session_params) do
    with {:ok, config} <- config(redirect_uri),
         {:ok, %{user: user}} <-
           config
           |> Keyword.put(:code_verifier, true)
           |> Keyword.put(:session_params, session_params)
           |> Google.callback(params) do
      normalize_identity(user)
    end
  end

  def normalize_identity(
        %{
          "sub" => provider_uid,
          "email" => email,
          "email_verified" => true
        } = claims
      )
      when is_binary(provider_uid) and provider_uid != "" and is_binary(email) and email != "" do
    normalized_email = email |> String.trim() |> String.downcase()

    {:ok,
     %{
       provider_uid: provider_uid,
       email: normalized_email,
       email_verified: true,
       display_name: display_name(normalized_email, Map.get(claims, "name"))
     }}
  end

  def normalize_identity(%{"email_verified" => false}), do: {:error, :email_not_verified}
  def normalize_identity(_claims), do: {:error, :invalid_provider_identity}

  defp config(redirect_uri) do
    case provider_config() do
      nil -> {:error, :provider_disabled}
      config -> {:ok, Keyword.put(config, :redirect_uri, redirect_uri)}
    end
  end

  defp provider_config do
    case Application.get_env(:who_need_help, :google_auth, []) do
      config when is_list(config) and config != [] -> config
      _disabled -> nil
    end
  end

  defp display_name(email, name) when is_binary(name) do
    case String.trim(name) do
      "" -> fallback_display_name(email)
      value -> String.slice(value, 0, 80)
    end
  end

  defp display_name(email, _name), do: fallback_display_name(email)

  defp fallback_display_name(email) do
    candidate = email |> String.split("@", parts: 2) |> hd() |> String.trim()

    if String.length(candidate) >= 2,
      do: String.slice(candidate, 0, 80),
      else: "Google user"
  end

  defp random_url_token(length) do
    length
    |> :crypto.strong_rand_bytes()
    |> Base.url_encode64(padding: false)
  end
end
