defmodule Mobilizon.Federation.ActivityStream.Converter.Media do
  @moduledoc """
  Media converter.

  This module allows to convert events from ActivityStream format to our own
  internal one, and back.
  """

  alias Mobilizon.Federation.ActivityStream
  alias Mobilizon.Medias
  alias Mobilizon.Medias.Media, as: MediaModel

  alias Mobilizon.Web.Upload

  @http_options [
    ssl: [{:versions, [:"tlsv1.2"]}]
  ]

  @doc """
  Convert a media struct to an ActivityStream representation.
  """
  @spec model_to_as(MediaModel.t()) :: ActivityStream.t()
  def model_to_as(%MediaModel{file: file}) do
    %{
      "type" => "Document",
      "mediaType" => file.content_type,
      "url" => file.url,
      "name" => file.name
    }
  end

  @doc """
  Save media data from raw data and return AS Link data.
  """
  @spec find_or_create_media(map(), String.t() | integer()) ::
          {:ok, MediaModel.t()} | {:error, atom() | String.t() | Ecto.Changeset.t()}
  def find_or_create_media(%{"type" => "Link", "href" => url}, actor_id),
    do:
      find_or_create_media(
        %{"type" => "Document", "url" => url, "name" => "External media"},
        actor_id
      )

  def find_or_create_media(
        %{"type" => "Document", "url" => media_url, "name" => name},
        actor_id
      )
      when is_binary(media_url) do
    with {:ok, %{url: url} = uploaded} <- upload_media(media_url, name) do
      case Medias.get_media_by_url(url) do
        %MediaModel{file: _file} = media ->
          {:ok, media}

        nil ->
          Medias.create_media(%{
            file: Map.take(uploaded, [:url, :name, :content_type, :size]),
            metadata: Map.take(uploaded, [:width, :height, :blurhash]),
            actor_id: actor_id
          })
      end
    end
  end

  @spec upload_media(String.t(), String.t()) :: {:ok, map()} | {:error, atom() | String.t()}
  defp upload_media(media_url, ""), do: upload_media(media_url, "unknown")

  defp upload_media(media_url, name) do
    case Tesla.get(media_url, opts: @http_options) do
      {:ok, %{body: body}} ->
        case Upload.store(%{body: body, name: name}) do
          {:ok, %{url: _url} = uploaded} ->
            {:ok, uploaded}

          {:error, err} ->
            {:error, err}
        end

      {:error, err} ->
        {:error, err}
    end
  end
end
