lib/wechat/setup.ex

defmodule WeChat.Setup do
  @moduledoc "Setup WeChat clients"
  alias WeChat.{Work, HubClient, HubServer}
  alias Work.Agent, as: WorkAgent

  @type options :: %{
          optional(:hub_springboard_url) => HubClient.hub_springboard_url(),
          optional(:oauth2_callbacks) => HubServer.oauth2_callbacks()
        }
  @type work_options :: %{all: options} | [all: options] | [{Work.agent(), options}]

  @app :wechat

  @spec setup_clients([{WeChat.client(), options | work_options}]) :: list
  def setup_clients(clients) do
    for {client, options} <- clients, is_atom(client) do
      if match?(:work, client.app_type()) do
        setup_work_client(client, _agents = options)
      else
        setup_client(client, options)
      end
    end
  end

  @spec setup_client(WeChat.client(), options) :: :ok
  def setup_client(client, options) do
    %{hub_springboard_url: hub_springboard_url, oauth2_callbacks: oauth2_callbacks} =
      replace_app(options, client)

    # hub_springboard_url set for hub client
    if hub_springboard_url do
      HubClient.set_hub_springboard_url(client, hub_springboard_url)
    end

    # oauth2_callbacks set for hub server
    if oauth2_callbacks do
      HubServer.set_oauth2_callbacks(client, oauth2_callbacks)
    end

    :ok
  end

  @spec setup_work_client(Work.client(), work_options) :: :ok
  def setup_work_client(client, %{all: options}) do
    setup_work_client(client, all: options)
  end

  def setup_work_client(client, all: options) do
    agents = Enum.map(client.agents(), fn %{id: id, name: name} -> {name || id, options} end)
    setup_work_client(client, agents)
  end

  def setup_work_client(client, agents) do
    WorkAgent.maybe_init_work_agents(client)

    for {agent, options} <- agents do
      setup_work_agent(client, agent, options)
    end

    :ok
  end

  @spec setup_work_agent(Work.client(), Work.agent() | WorkAgent.t(), options) :: :ok
  def setup_work_agent(client, agent, options) when is_struct(agent, WorkAgent) do
    setup_work_agent(client, agent.name || agent.id, options)
  end

  def setup_work_agent(client, agent, options) do
    %{hub_springboard_url: hub_springboard_url, oauth2_callbacks: oauth2_callbacks} =
      options |> replace_app(client) |> replace_agent(agent)

    # hub_springboard_url set for hub client
    if hub_springboard_url do
      HubClient.set_hub_springboard_url(client, agent, hub_springboard_url)
    end

    # oauth2_callbacks set for hub server
    if oauth2_callbacks do
      HubServer.set_oauth2_callbacks(client, agent, oauth2_callbacks)
    end

    :ok
  end

  defp replace_app(options, client) do
    env = Application.get_env(@app, :env, "dev") |> to_string()

    options
    |> Map.new()
    |> Enum.into(%{hub_springboard_url: nil, oauth2_callbacks: nil})
    |> replace(":app", client.code_name())
    |> replace_hub_springboard_url(":env", env)
  end

  defp replace_agent(options, agent), do: replace(options, ":agent", to_string(agent))

  defp replace(
         %{hub_springboard_url: hub_springboard_url, oauth2_callbacks: oauth2_callbacks},
         match,
         replacement
       ) do
    hub_springboard_url =
      if hub_springboard_url do
        String.replace(hub_springboard_url, match, replacement)
      end

    oauth2_callbacks =
      if oauth2_callbacks do
        for {env, url} <- oauth2_callbacks do
          {env, String.replace(url, match, replacement)}
        end
      end

    %{hub_springboard_url: hub_springboard_url, oauth2_callbacks: oauth2_callbacks}
  end

  defp replace_hub_springboard_url(
         %{hub_springboard_url: hub_springboard_url} = options,
         match,
         replacement
       ) do
    if hub_springboard_url do
      %{options | hub_springboard_url: String.replace(hub_springboard_url, match, replacement)}
    else
      options
    end
  end
end