# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors 
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus.OStatusController do
  use Pleroma.Web, :controller
  alias Fallback.RedirectController
  alias Pleroma.Activity
  alias Pleroma.Object
  alias Pleroma.User
  alias Pleroma.Web.ActivityPub.ActivityPub
  alias Pleroma.Web.ActivityPub.ActivityPubController
  alias Pleroma.Web.ActivityPub.ObjectView
  alias Pleroma.Web.ActivityPub.Visibility
  alias Pleroma.Web.Endpoint
  alias Pleroma.Web.Federator
  alias Pleroma.Web.Metadata.PlayerView
  alias Pleroma.Web.OStatus
  alias Pleroma.Web.OStatus.ActivityRepresenter
  alias Pleroma.Web.OStatus.FeedRepresenter
  alias Pleroma.Web.Router
  alias Pleroma.Web.XML
  plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
  plug(
    Pleroma.Plugs.SetFormatPlug
    when action in [:feed_redirect, :object, :activity, :notice]
  )
  action_fallback(:errors)
  def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
    with {_, %User{} = user} <-
           {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
      RedirectController.redirector_with_meta(conn, %{user: user})
    end
  end
  def feed_redirect(%{assigns: %{format: format}} = conn, _params)
      when format in ["json", "activity+json"] do
    ActivityPubController.call(conn, :user)
  end
  def feed_redirect(conn, %{"nickname" => nickname}) do
    with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
      redirect(conn, external: OStatus.feed_path(user))
    end
  end
  def feed(conn, %{"nickname" => nickname} = params) do
    with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
      query_params =
        Map.take(params, ["max_id"])
        |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
      activities =
        ActivityPub.fetch_public_activities(query_params)
        |> Enum.reverse()
      response =
        user
        |> FeedRepresenter.to_simple_form(activities, [user])
        |> :xmerl.export_simple(:xmerl_xml)
        |> to_string
      conn
      |> put_resp_content_type("application/atom+xml")
      |> send_resp(200, response)
    end
  end
  defp decode_or_retry(body) do
    with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
         {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
      {:ok, doc}
    else
      _e ->
        with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
             doc <- XML.parse_document(decoded),
             uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
             {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
             {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
             {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
          {:ok, doc}
        end
    end
  end
  def salmon_incoming(conn, _) do
    {:ok, body, _conn} = read_body(conn)
    {:ok, doc} = decode_or_retry(body)
    Federator.incoming_doc(doc)
    conn
    |> send_resp(200, "")
  end
  def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
      when format in ["json", "activity+json"] do
    ActivityPubController.call(conn, :object)
  end
  def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
    with id <- o_status_url(conn, :object, uuid),
         {_, %Activity{} = activity} <-
           {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
         {_, true} <- {:public?, Visibility.is_public?(activity)},
         %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
      case format do
        "html" -> redirect(conn, to: "/notice/#{activity.id}")
        _ -> represent_activity(conn, nil, activity, user)
      end
    else
      reason when reason in [{:public?, false}, {:activity, nil}] ->
        {:error, :not_found}
      e ->
        e
    end
  end
  def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
      when format in ["json", "activity+json"] do
    ActivityPubController.call(conn, :activity)
  end
  def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
    with id <- o_status_url(conn, :activity, uuid),
         {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
         {_, true} <- {:public?, Visibility.is_public?(activity)},
         %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
      case format do
        "html" -> redirect(conn, to: "/notice/#{activity.id}")
        _ -> represent_activity(conn, format, activity, user)
      end
    else
      reason when reason in [{:public?, false}, {:activity, nil}] ->
        {:error, :not_found}
      e ->
        e
    end
  end
  def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
    with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
         {_, true} <- {:public?, Visibility.is_public?(activity)},
         %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
      cond do
        format == "html" && activity.data["type"] == "Create" ->
          %Object{} = object = Object.normalize(activity)
          RedirectController.redirector_with_meta(
            conn,
            %{
              activity_id: activity.id,
              object: object,
              url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
              user: user
            }
          )
        format == "html" ->
          RedirectController.redirector(conn, nil)
        true ->
          represent_activity(conn, format, activity, user)
      end
    else
      reason when reason in [{:public?, false}, {:activity, nil}] ->
        conn
        |> put_status(404)
        |> RedirectController.redirector(nil, 404)
      e ->
        e
    end
  end
  # Returns an HTML embedded