defmodule Mobilizon.Web.FeedController do
  @moduledoc """
  Controller to serve RSS, ATOM and iCal Feeds
  """
  use Mobilizon.Web, :controller
  plug(:put_layout, false)
  action_fallback(Mobilizon.Web.FallbackController)
  alias Mobilizon.Config
  require Logger

  @spec instance(Plug.Conn.t(), map()) :: Plug.Conn.t()
  def instance(conn, %{"format" => format}) do
    if Config.get([:instance, :enable_instance_feeds], false) do
      return_data(conn, format, "instance", Config.instance_name())
    else
      send_resp(conn, 401, "Instance feeds are not enabled.")
    end
  end

  @spec actor(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, :not_found}
  def actor(conn, %{"format" => format, "name" => name}) do
    return_data(conn, format, "actor_" <> name, name)
  end

  def actor(_conn, _) do
    {:error, :not_found}
  end

  @spec event(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, :not_found}
  def event(conn, %{"uuid" => uuid, "format" => "ics"}) do
    return_data(conn, "ics", "event_" <> uuid, "event")
  end

  def event(_conn, _) do
    {:error, :not_found}
  end

  @spec going(Plug.Conn.t(), map()) :: Plug.Conn.t() | {:error, :not_found}
  def going(conn, %{"token" => token, "format" => format}) do
    return_data(conn, format, "token_" <> token, "events")
  end

  def going(_conn, _) do
    {:error, :not_found}
  end

  @spec return_data(Plug.Conn.t(), String.t(), String.t(), String.t()) ::
          Plug.Conn.t() | {:error, :not_found}
  defp return_data(conn, "atom", key, filename) do
    case Cachex.fetch(:feed, key) do
      {status, data} when status in [:commit, :ok] ->
        conn
        |> put_resp_content_type("application/atom+xml")
        |> put_resp_header(
          "content-disposition",
          "attachment; filename=\"#{filename}.atom\""
        )
        |> send_resp(200, data)

      # No need to log these two
      {:ignore, :actor_not_found} ->
        {:error, :not_found}

      {:ignore, :actor_not_public} ->
        {:error, :not_found}

      err ->
        Logger.warn("Unable to find feed data cached for key #{key}, returned #{inspect(err)}")
        {:error, :not_found}
    end
  end

  defp return_data(conn, "ics", key, filename) do
    case Cachex.fetch(:ics, key) do
      {status, data} when status in [:commit, :ok] ->
        conn
        |> put_resp_content_type("text/calendar")
        |> put_resp_header(
          "content-disposition",
          "attachment; filename=\"#{filename}.ics\""
        )
        |> send_resp(200, data)

      # No need to log these two
      {:ignore, :actor_not_found} ->
        {:error, :not_found}

      {:ignore, :actor_not_public} ->
        {:error, :not_found}

      err ->
        Logger.warn("Unable to find feed data cached for key #{key}, returned #{inspect(err)}")
        {:error, :not_found}
    end
  end

  defp return_data(_conn, _, _, _) do
    {:error, :not_found}
  end
end
