defmodule GroupherServer.Test.Articles.Drink do
  use GroupherServer.TestTools

  import Helper.Utils, only: [get_config: 2]

  alias GroupherServer.{CMS, Repo}
  alias Helper.Converter.{EditorToHTML, HtmlSanitizer}

  alias EditorToHTML.{Class, Validator}
  alias CMS.Model.{Author, Drink, Community, ArticleDocument, DrinkDocument}
  alias Helper.ORM

  @root_class Class.article()
  @last_year Timex.shift(Timex.beginning_of_year(Timex.now()), days: -3, seconds: -1)
  @article_digest_length get_config(:article, :digest_length)

  setup do
    {:ok, user} = db_insert(:user)
    {:ok, user2} = db_insert(:user)
    {:ok, community} = db_insert(:community)

    drink_attrs = mock_attrs(:drink, %{community_id: community.id})

    {:ok, ~m(user user2 community drink_attrs)a}
  end

  describe "[cms drinks curd]" do
    test "can create drink with valid attrs", ~m(user community drink_attrs)a do
      assert {:error, _} = ORM.find_by(Author, user_id: user.id)
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      drink = Repo.preload(drink, :document)

      body_map = Jason.decode!(drink.document.body)

      assert drink.meta.thread == "DRINK"

      assert drink.title == drink_attrs.title
      assert body_map |> Validator.is_valid()

      assert drink.document.body_html
             |> String.contains?(~s(<div class="#{@root_class["viewer"]}">))

      assert drink.document.body_html |> String.contains?(~s(<p id="block-))

      paragraph_text = body_map["blocks"] |> List.first() |> get_in(["data", "text"])

      assert drink.digest ==
               paragraph_text
               |> HtmlSanitizer.strip_all_tags()
               |> String.slice(0, @article_digest_length)
    end

    test "created drink should have a acitve_at field, same with inserted_at",
         ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)

      assert drink.active_at == drink.inserted_at
    end

    test "read drink should update views and meta viewed_user_list",
         ~m(drink_attrs community user user2)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      {:ok, _} = CMS.read_article(:drink, drink.id, user)
      {:ok, _created} = ORM.find(Drink, drink.id)

      # same user duplicate case
      {:ok, _} = CMS.read_article(:drink, drink.id, user)
      {:ok, created} = ORM.find(Drink, drink.id)

      assert created.meta.viewed_user_ids |> length == 1
      assert user.id in created.meta.viewed_user_ids

      {:ok, _} = CMS.read_article(:drink, drink.id, user2)
      {:ok, created} = ORM.find(Drink, drink.id)

      assert created.meta.viewed_user_ids |> length == 2
      assert user.id in created.meta.viewed_user_ids
      assert user2.id in created.meta.viewed_user_ids
    end

    test "read drink should contains viewer_has_xxx state",
         ~m(drink_attrs community user user2)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      {:ok, drink} = CMS.read_article(:drink, drink.id, user)

      assert not drink.viewer_has_collected
      assert not drink.viewer_has_upvoted
      assert not drink.viewer_has_reported

      {:ok, drink} = CMS.read_article(:drink, drink.id)

      assert not drink.viewer_has_collected
      assert not drink.viewer_has_upvoted
      assert not drink.viewer_has_reported

      {:ok, drink} = CMS.read_article(:drink, drink.id, user2)

      assert not drink.viewer_has_collected
      assert not drink.viewer_has_upvoted
      assert not drink.viewer_has_reported

      {:ok, _} = CMS.upvote_article(:drink, drink.id, user)
      {:ok, _} = CMS.collect_article(:drink, drink.id, user)
      {:ok, _} = CMS.report_article(:drink, drink.id, "reason", "attr_info", user)

      {:ok, drink} = CMS.read_article(:drink, drink.id, user)

      assert drink.viewer_has_collected
      assert drink.viewer_has_upvoted
      assert drink.viewer_has_reported
    end

    test "create drink with an exsit community fails", ~m(user)a do
      invalid_attrs = mock_attrs(:drink, %{community_id: non_exsit_id()})
      ivalid_community = %Community{id: non_exsit_id()}

      assert {:error, _} = CMS.create_article(ivalid_community, :drink, invalid_attrs, user)
    end
  end

  describe "[cms drink sink/undo_sink]" do
    test "if a drink is too old, read drink should update can_undo_sink flag",
         ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)

      assert drink.meta.can_undo_sink

      {:ok, drink_last_year} = db_insert(:drink, %{title: "last year", inserted_at: @last_year})
      {:ok, drink_last_year} = CMS.read_article(:drink, drink_last_year.id)
      assert not drink_last_year.meta.can_undo_sink

      {:ok, drink_last_year} = CMS.read_article(:drink, drink_last_year.id, user)
      assert not drink_last_year.meta.can_undo_sink
    end

    test "can sink a drink", ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      assert not drink.meta.is_sinked

      {:ok, drink} = CMS.sink_article(:drink, drink.id)
      assert drink.meta.is_sinked
      assert drink.active_at == drink.inserted_at
    end

    test "can undo sink drink", ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      {:ok, drink} = CMS.sink_article(:drink, drink.id)
      assert drink.meta.is_sinked
      assert drink.meta.last_active_at == drink.active_at

      {:ok, drink} = CMS.undo_sink_article(:drink, drink.id)
      assert not drink.meta.is_sinked
      assert drink.active_at == drink.meta.last_active_at
    end

    test "can not undo sink to old drink", ~m()a do
      {:ok, drink_last_year} = db_insert(:drink, %{title: "last year", inserted_at: @last_year})

      {:error, reason} = CMS.undo_sink_article(:drink, drink_last_year.id)
      is_error?(reason, :undo_sink_old_article)
    end
  end

  describe "[cms drink document]" do
    test "will create related document after create", ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      {:ok, drink} = CMS.read_article(:drink, drink.id)
      assert not is_nil(drink.document.body_html)
      {:ok, drink} = CMS.read_article(:drink, drink.id, user)
      assert not is_nil(drink.document.body_html)

      {:ok, article_doc} = ORM.find_by(ArticleDocument, %{article_id: drink.id, thread: "DRINK"})
      {:ok, drink_doc} = ORM.find_by(DrinkDocument, %{drink_id: drink.id})

      assert drink.document.body == drink_doc.body
      assert article_doc.body == drink_doc.body
    end

    test "delete drink should also delete related document", ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)
      {:ok, _article_doc} = ORM.find_by(ArticleDocument, %{article_id: drink.id, thread: "DRINK"})
      {:ok, _drink_doc} = ORM.find_by(DrinkDocument, %{drink_id: drink.id})

      {:ok, _} = CMS.delete_article(drink)

      {:error, _} = ORM.find(Drink, drink.id)
      {:error, _} = ORM.find_by(ArticleDocument, %{article_id: drink.id, thread: "DRINK"})
      {:error, _} = ORM.find_by(DrinkDocument, %{drink_id: drink.id})
    end

    test "update drink should also update related document", ~m(user community drink_attrs)a do
      {:ok, drink} = CMS.create_article(community, :drink, drink_attrs, user)

      body = mock_rich_text(~s(new content))
      {:ok, drink} = CMS.update_article(drink, %{body: body})

      {:ok, article_doc} = ORM.find_by(ArticleDocument, %{article_id: drink.id, thread: "DRINK"})
      {:ok, drink_doc} = ORM.find_by(DrinkDocument, %{drink_id: drink.id})

      assert String.contains?(drink_doc.body, "new content")
      assert String.contains?(article_doc.body, "new content")
    end
  end
end
