defmodule Mobilizon.Service.SiteMap do
  @moduledoc """
  Generates a sitemap
  """

  alias Mobilizon.{Actors, Events, Posts}
  alias Mobilizon.Storage.Repo
  alias Mobilizon.Web.Endpoint
  alias Mobilizon.Web.Router.Helpers, as: Routes

  @default_static_frequency :monthly

  @spec generate_sitemap :: {:ok, :ok}
  def generate_sitemap do
    static_routes = [
      {Routes.page_url(Endpoint, :index, []), :daily},
      "#{Endpoint.url()}/search",
      "#{Endpoint.url()}/about/instance",
      "#{Endpoint.url()}/terms",
      "#{Endpoint.url()}/privacy",
      "#{Endpoint.url()}/rules",
      "#{Endpoint.url()}/glossary"
    ]

    config = [
      store: Sitemapper.FileStore,
      store_config: [path: Application.app_dir(:mobilizon, "priv/static")],
      sitemap_url: Endpoint.url(),
      gzip: false
    ]

    Repo.transaction(
      fn ->
        Events.stream_events_for_sitemap()
        |> Stream.concat(Actors.list_groups_for_stream())
        |> Stream.concat(Posts.list_posts_for_stream())
        |> Stream.concat(
          Enum.map(static_routes, fn route ->
            {url, frequency} =
              case route do
                {url, frequency} -> {url, frequency}
                url when is_binary(url) -> {url, @default_static_frequency}
              end

            %{url: url, updated_at: nil, frequence: frequency}
          end)
        )
        |> Stream.map(fn %{url: url, updated_at: updated_at} = args ->
          frequence = Map.get(args, :frequence, :weekly)

          %Sitemapper.URL{
            loc: url,
            changefreq: frequence,
            lastmod: check_date_time(updated_at)
          }
        end)
        |> Sitemapper.generate(config)
        |> Sitemapper.persist(config)
        |> Sitemapper.ping(config)
        |> Stream.run()
      end,
      timeout: :infinity
    )
  end

  # Sometimes we use naive datetimes
  @spec check_date_time(any()) :: DateTime.t() | nil
  defp check_date_time(%NaiveDateTime{} = datetime), do: DateTime.from_naive!(datetime, "Etc/UTC")
  defp check_date_time(%DateTime{} = datetime), do: datetime
  defp check_date_time(_), do: nil
end