defmodule Plausible.Session.TransferTest do
  use ExUnit.Case
  import Plausible.Factory
  import Plausible.TestUtils, only: [tmp_dir: 0]

  @tag :slow
  test "it works" do
    tmp_dir = tmp_dir()

    old = start_another_plausible(tmp_dir)
    await_transfer(old)

    Enum.each(1..250, fn _ -> process_event(old, build(:event, name: "pageview")) end)

    new = start_another_plausible(tmp_dir)
    await_transfer(new)

    assert all_sessions_sorted(new) == all_sessions_sorted(old)
  end

  defp start_another_plausible(tmp_dir) do
    {:ok, pid, _node} = :peer.start_link(%{connection: {{127, 0, 0, 1}, 0}})
    add_code_paths(pid)
    transfer_configuration(pid)
    :ok = :peer.call(pid, Application, :put_env, [:plausible, :session_transfer_dir, tmp_dir])
    ensure_applications_started(pid)
    pid
  end

  defp add_code_paths(pid) do
    :ok = :peer.call(pid, :code, :add_paths, [:code.get_path()])
  end

  defp transfer_configuration(pid) do
    for {app_name, _, _} <- Application.loaded_applications() do
      for {key, val} <- Application.get_all_env(app_name) do
        :ok = :peer.call(pid, Application, :put_env, [app_name, key, val])
      end
    end
  end

  defp ensure_applications_started(pid) do
    {:ok, _apps} = :peer.call(pid, Application, :ensure_all_started, [:mix])
    :ok = :peer.call(pid, Mix, :env, [Mix.env()])

    for {app_name, _, _} <- Application.loaded_applications(), app_name != :dialyxir do
      {:ok, _apps} = :peer.call(pid, Application, :ensure_all_started, [app_name])
    end
  end

  @session_params %{
    referrer: "ref",
    referrer_source: "refsource",
    utm_medium: "medium",
    utm_source: "source",
    utm_campaign: "campaign",
    utm_content: "content",
    utm_term: "term",
    browser: "browser",
    browser_version: "55",
    country_code: "EE",
    screen_size: "Desktop",
    operating_system: "Mac",
    operating_system_version: "11"
  }

  defp process_event(pid, event) do
    :peer.call(pid, Plausible.Session.CacheStore, :on_event, [
      event,
      @session_params,
      _prev_user_id = nil,
      [buffer_insert: &Function.identity/1]
    ])
  end

  defp all_sessions_sorted(pid) do
    cache_names = :peer.call(pid, Plausible.Cache.Adapter, :get_names, [:sessions])

    records =
      Enum.flat_map(cache_names, fn cache_name ->
        tab = :peer.call(pid, ConCache, :ets, [cache_name])
        :peer.call(pid, :ets, :tab2list, [tab])
      end)

    Enum.sort_by(records, fn {key, _} -> key end)
  end

  defp await_transfer(pid, timeout \\ :timer.seconds(1)) do
    test = self()

    spawn_link(fn ->
      await_loop(fn -> :peer.call(pid, Plausible.Session.Transfer, :attempted?, []) end)
      send(test, :took)
    end)

    assert_receive :took, timeout
  end

  defp await_loop(f) do
    :timer.sleep(100)

    case f.() do
      true -> :done
      false -> await_loop(f)
    end
  end
end
