defmodule Segment.Analytics.Sender do
  @moduledoc """
    The `Segment.Analytics.Sender` service implementation is an alternative to the default Batcher to send every event as it is called.
    The HTTP call is made with an async `Task` to not block the GenServer. This will not guarantee ordering.

    The `Segment.Analytics.Batcher` should be preferred in production but this module will emulate the implementation of the original library if
    you need that or need events to be as real-time as possible.
  """
  use GenServer
  alias Segment.Analytics.{Track, Identify, Screen, Alias, Group, Page}

  @doc """
    Start the `Segment.Analytics.Sender` GenServer with an Segment HTTP Source API Write Key
  """
  @spec start_link(String.t()) :: GenServer.on_start()
  def start_link(api_key) do
    client = Segment.Http.client(api_key)
    GenServer.start_link(__MODULE__, client, name: __MODULE__)
  end

  @doc """
    Start the `Segment.Analytics.Sender` GenServer with an Segment HTTP Source API Write Key and a Tesla Adapter. This is mainly used
    for testing purposes to override the Adapter with a Mock.
  """
  @spec start_link(String.t(), Segment.Http.adapter()) :: GenServer.on_start()
  def start_link(api_key, adapter) do
    client = Segment.Http.client(api_key, adapter)
    GenServer.start_link(__MODULE__, {client, :queue.new()}, name: __MODULE__)
  end

  # client
  @doc """
    Make a call to Segment with an event. Should be of type `Track, Identify, Screen, Alias, Group or Page`.
    This event will be sent immediately and asynchronously
  """
  @spec call(Segment.segment_event()) :: :ok
  def call(%{__struct__: mod} = event)
      when mod in [Track, Identify, Screen, Alias, Group, Page] do
    callp(event)
  end

  # GenServer Callbacks

  @impl true
  def init(client) do
    {:ok, client}
  end

  @impl true
  def handle_cast({:send, event}, client) do
    Task.start_link(fn -> Segment.Http.send(client, event) end)
    {:noreply, client}
  end

  # Helpers
  defp callp(event) do
    GenServer.cast(__MODULE__, {:send, event})
  end
end
