defmodule Forum.LogStore do
  @moduledoc """
  Persists request and hosting logs to a JSON file.
  """
  use GenServer

  @max_entries 1_000

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  def add(entry) when is_map(entry) do
    GenServer.cast(__MODULE__, {:add, entry})
  end

  def list(limit \\ 200) do
    GenServer.call(__MODULE__, {:list, limit})
  end

  @impl true
  def init(_opts) do
    path = Path.expand("priv/logs/requests.json", File.cwd!())
    File.mkdir_p!(Path.dirname(path))

    {:ok, %{path: path, entries: read_entries(path)}}
  end

  @impl true
  def handle_cast({:add, entry}, state) do
    entry =
      entry
      |> Map.put_new("id", System.unique_integer([:positive, :monotonic]))
      |> Map.put_new("time", DateTime.utc_now() |> DateTime.to_iso8601())

    entries = [entry | state.entries] |> Enum.take(@max_entries)
    write_entries!(state.path, entries)

    {:noreply, %{state | entries: entries}}
  end

  @impl true
  def handle_call({:list, limit}, _from, state) do
    {:reply, Enum.take(state.entries, limit), state}
  end

  defp read_entries(path) do
    with {:ok, raw} <- File.read(path),
         {:ok, entries} when is_list(entries) <- Jason.decode(raw) do
      entries
    else
      _ -> []
    end
  end

  defp write_entries!(path, entries) do
    tmp_path = path <> ".tmp"
    File.write!(tmp_path, Jason.encode!(entries, pretty: true))
    File.rename!(tmp_path, path)
  end
end
