defmodule Mobilizon.Reports.Report do
  @moduledoc """
  Represents a report entity.
  """

  use Ecto.Schema

  import Ecto.Changeset

  alias Mobilizon.Actors.Actor
  alias Mobilizon.Discussions.Comment
  alias Mobilizon.Events.Event
  alias Mobilizon.Reports.{Note, ReportStatus}

  alias Mobilizon.Web.Endpoint

  @type t :: %__MODULE__{
          id: integer(),
          content: String.t(),
          status: atom(),
          url: String.t(),
          reported: Actor.t(),
          reporter: Actor.t(),
          manager: Actor.t(),
          events: [Event.t()],
          comments: [Comment.t()],
          notes: [Note.t()]
        }

  @required_attrs [:url, :reported_id, :reporter_id]
  @optional_attrs [:content, :status, :manager_id, :local]
  @attrs @required_attrs ++ @optional_attrs

  @timestamps_opts [type: :utc_datetime]

  @derive {Jason.Encoder, only: [:status, :url]}
  schema "reports" do
    field(:content, :string)
    field(:status, ReportStatus, default: :open)
    field(:url, :string)
    field(:local, :boolean, default: true)

    # The reported actor
    belongs_to(:reported, Actor)
    # The actor who reported
    belongs_to(:reporter, Actor)
    # The actor who last acted on this report
    belongs_to(:manager, Actor)
    # The eventual Events inside the report
    many_to_many(:events, Event, join_through: "reports_events", on_replace: :delete)
    # The eventual Comments inside the report
    many_to_many(:comments, Comment, join_through: "reports_comments", on_replace: :delete)
    # The notes associated to the report
    has_many(:notes, Note, foreign_key: :report_id)

    timestamps()
  end

  @doc false
  @spec changeset(t | Ecto.Schema.t(), map) :: Ecto.Changeset.t()
  def changeset(%__MODULE__{} = report, attrs) do
    report
    |> cast(attrs, @attrs)
    |> maybe_generate_url()
    |> maybe_put_events(attrs)
    |> maybe_put_comments(attrs)
    |> validate_required(@required_attrs)
  end

  defp maybe_put_comments(%Ecto.Changeset{} = changeset, %{comments: comments}) do
    put_assoc(changeset, :comments, comments)
  end

  defp maybe_put_comments(%Ecto.Changeset{} = changeset, _), do: changeset

  defp maybe_put_events(%Ecto.Changeset{} = changeset, %{events: events}) do
    put_assoc(changeset, :events, events)
  end

  defp maybe_put_events(%Ecto.Changeset{} = changeset, _), do: changeset

  @spec maybe_generate_url(Ecto.Changeset.t()) :: Ecto.Changeset.t()
  defp maybe_generate_url(%Ecto.Changeset{} = changeset) do
    with res when res in [:error, {:data, nil}] <- fetch_field(changeset, :url),
         url <- "#{Endpoint.url()}/report/#{Ecto.UUID.generate()}" do
      put_change(changeset, :url, url)
    else
      _ -> changeset
    end
  end
end