defmodule PhilomenaWeb.UserIpUpdater do
  alias Philomena.UserIps.UserIp
  alias Philomena.Repo
  import Ecto.Query

  def child_spec([]) do
    %{
      id: PhilomenaWeb.UserIpUpdater,
      start: {PhilomenaWeb.UserIpUpdater, :start_link, [[]]}
    }
  end

  def start_link([]) do
    {:ok, spawn_link(&init/0)}
  end

  def cast(user_id, ip_address, updated_at) do
    pid = Process.whereis(:ip_updater)
    if pid, do: send(pid, {user_id, ip_address, updated_at})
  end

  defp init do
    Process.register(self(), :ip_updater)
    run()
  end

  defp run do
    user_ips = Enum.map(receive_all(), &into_insert_all/1)

    update_query =
      update(UserIp, inc: [uses: 1], set: [updated_at: fragment("EXCLUDED.updated_at")])

    Repo.insert_all(UserIp, user_ips, on_conflict: update_query, conflict_target: [:user_id, :ip])

    :timer.sleep(:timer.seconds(60))

    run()
  end

  defp receive_all(user_ips \\ %{}) do
    receive do
      {user_id, ip_address, updated_at} ->
        user_ips
        |> Map.put({user_id, ip_address}, updated_at)
        |> receive_all()
    after
      0 ->
        user_ips
    end
  end

  defp into_insert_all({{user_id, ip_address}, updated_at}) do
    %{
      user_id: user_id,
      ip: cast_ip(ip_address),
      uses: 1,
      created_at: updated_at,
      updated_at: updated_at
    }
  end

  # There exists no EctoNetwork.INET.cast!/1
  defp cast_ip(ip), do: elem(EctoNetwork.INET.cast(ip), 1)
end