defmodule Mobilizon.Service.Statistics do
  @moduledoc """
  A module that provides cached statistics
  """

  alias Mobilizon.{Actors, Discussions, Events, Users}
  alias Mobilizon.Events.Categories
  alias Mobilizon.Federation.ActivityPub.Relay

  @spec get_cached_value(String.t()) :: any() | nil
  def get_cached_value(key) do
    case Cachex.fetch(:statistics, key, fn key ->
           case create_cache(key) do
             value when not is_nil(value) -> {:commit, value}
             err -> {:ignore, err}
           end
         end) do
      {status, value} when status in [:ok, :commit] -> value
      _err -> nil
    end
  end

  defp create_cache(:local_users) do
    Users.count_users()
  end

  defp create_cache(:local_events) do
    Events.count_local_events()
  end

  defp create_cache(:confirmed_participations_to_local_events) do
    Events.count_confirmed_participants_for_local_events()
  end

  defp create_cache(:local_comments) do
    Discussions.count_local_comments_under_events()
  end

  defp create_cache(:local_groups) do
    Actors.count_local_groups()
  end

  defp create_cache(:federation_events) do
    Events.count_events()
  end

  defp create_cache(:federation_comments) do
    Discussions.count_comments_under_events()
  end

  defp create_cache(:federation_groups) do
    Actors.count_groups()
  end

  defp create_cache(:instance_followers) do
    relay_actor = Relay.get_actor()
    Actors.count_followers_for_actor(relay_actor)
  end

  defp create_cache(:instance_followings) do
    relay_actor = Relay.get_actor()
    Actors.count_followings_for_actor(relay_actor)
  end

  @spec category_statistics :: list({String.t(), non_neg_integer()})
  def category_statistics do
    case Cachex.fetch(:statistics, :categories, fn ->
           allowed_categories =
             Categories.list()
             |> Enum.map(fn %{id: category} -> category |> Atom.to_string() |> String.upcase() end)

           statistics =
             Events.category_statistics()
             |> Enum.filter(fn {category, _} -> category in allowed_categories end)
             |> Enum.map(fn {category, number} -> %{key: category, number: number} end)

           {:commit, statistics}
         end) do
      {status, value} when status in [:ok, :commit] -> value
      _err -> nil
    end
  end
end