defmodule Mobilizon.Service.Export.Participants.PDF do
  @moduledoc """
  Export a list of participants to PDF
  """

  alias Mobilizon.{Events, Export, PythonPort, PythonWorker}
  alias Mobilizon.Events.Event
  alias Mobilizon.Storage.Repo
  alias Mobilizon.Web.ExportView
  alias Mobilizon.Web.Gettext, as: GettextBackend
  alias Phoenix.HTML.Safe
  import Mobilizon.Web.Gettext, only: [gettext: 2]

  import Mobilizon.Service.Export.Participants.Common,
    only: [
      save_upload: 5,
      columns: 0,
      to_list: 1,
      clean_exports: 2,
      export_enabled?: 1,
      export_path: 1
    ]

  @extension "pdf"

  def extension do
    @extension
  end

  @spec export(Event.t(), Keyword.t()) ::
          {:ok, String.t()} | {:error, :failed_to_save_upload | :export_dependency_not_installed}
  def export(%Event{id: event_id} = event, options \\ []) do
    if ready?() do
      filename = "#{ShortUUID.encode!(Ecto.UUID.generate())}.pdf"
      full_path = Path.join([export_path(@extension), filename])

      case Repo.transaction(
             fn ->
               content =
                 event_id
                 |> Events.participant_for_event_export_query(Keyword.get(options, :roles, []))
                 |> Repo.all()
                 |> Enum.map(&to_list/1)
                 |> render_template(event, Keyword.get(options, :locale, "en"))
                 |> generate_pdf()

               File.write!(full_path, content)

               with {:error, err} <- save_pdf_upload(full_path, filename, event) do
                 Repo.rollback(err)
               end
             end,
             timeout: :infinity
           ) do
        {:error, _err} ->
          File.rm!(full_path)
          {:error, :failed_to_save_upload}

        {:ok, _ok} ->
          {:ok, filename}
      end
    else
      {:error, :export_dependency_not_installed}
    end
  end

  @spec render_template(list(), Event.t(), String.t()) :: String.t()
  defp render_template(data, %Event{} = event, locale) do
    Gettext.put_locale(locale)

    ExportView.render("event_participants.html",
      data: data,
      columns: columns(),
      event: event,
      locale: locale
    )
    |> Safe.to_iodata()
    |> IO.iodata_to_binary()
  end

  defp generate_pdf(html) do
    PythonWorker.generate_pdf(html)
  end

  @spec save_pdf_upload(String.t(), String.t(), Event.t()) ::
          {:ok, Export.t()} | {:error, atom() | Ecto.Changeset.t()}
  defp save_pdf_upload(full_path, filename, %Event{id: event_id, title: title}) do
    GettextBackend.gettext_comment(
      "File name template for exported list of participants. Should NOT contain spaces. Make sure the output is going to be something standardized that is acceptable as a file name on most systems."
    )

    save_upload(
      full_path,
      filename,
      to_string(event_id),
      gettext("%{event}_participants", event: title) <> ".pdf",
      "pdf"
    )
  end

  @doc """
  Clean outdated files in export folder
  """
  @spec clean_exports :: :ok
  def clean_exports do
    clean_exports(@extension, export_path(@extension))
  end

  @spec dependencies_ok? :: boolean
  def dependencies_ok? do
    PythonPort.python_exists?() && PythonWorker.has_module("weasyprint")
  end

  @spec enabled? :: boolean
  def enabled? do
    export_enabled?(__MODULE__)
  end

  @spec ready? :: boolean
  def ready? do
    enabled?() && dependencies_ok?()
  end
end