defmodule Test.Core.PostsTest do
  use Test.DataCase, async: true

  describe "get!/1" do
    @tag post: :post
    test "returns the post with the given id", %{posts: %{post: post}} do
      assert result = Core.Posts.get!(post.id)
      assert %Schema.Post{} = result
      assert result.id == post.id
    end

    test "raises an Ecto.NoResultsError if there is no matching post" do
      assert_raise Ecto.NoResultsError, fn ->
        Core.Posts.get!(Faker.UUID.v4())
      end
    end
  end

  describe "get_by_publish_date_and_slug!/2" do
    @tag blog: :blog
    test "returns the post with the given publish date and slug", %{blogs: %{blog: blog}} do
      assert {:ok, blog} = Core.Posts.publish_post(blog)
      publish_date = DateTime.to_date(blog.published_at)
      assert result = Core.Posts.get_by_publish_date_and_slug!(publish_date, blog.slug)
      assert %Schema.Post{} = result
      assert result.id == blog.id
    end

    @tag blog: :blog
    test "raises an Ecto.NoResultsError if there is no matching post", %{blogs: %{blog: blog}} do
      assert_raise Ecto.NoResultsError, fn ->
        Core.Posts.get_by_publish_date_and_slug!(~D[1993-11-28], "first-day-on-earth")
      end

      assert {:ok, blog} = Core.Posts.publish_post(blog)

      assert_raise Ecto.NoResultsError, fn ->
        Core.Posts.get_by_publish_date_and_slug!(~D[1993-11-28], blog.slug)
      end
    end
  end

  describe "change_post/1/2" do
    test "returns a changeset" do
      assert %Ecto.Changeset{} = Core.Posts.change_post(%{})
      assert %Ecto.Changeset{} = Core.Posts.change_post(%Schema.Post{}, %{})

      assert %Ecto.Changeset{} =
               Test.Fixtures.Posts.blog_post(:blog)
               |> Core.Posts.change_post()

      assert %Ecto.Changeset{} =
               %Schema.Post{}
               |> Core.Posts.change_post(Test.Fixtures.Posts.blog_post(:blog))

      assert %Ecto.Changeset{} =
               Test.Fixtures.Posts.status_post(:status)
               |> Core.Posts.change_post()

      assert %Ecto.Changeset{} =
               %Schema.Post{}
               |> Core.Posts.change_post(Test.Fixtures.Posts.status_post(:status))
    end
  end

  describe "create_post/1" do
    test "creates a status post" do
      assert {:ok, %Schema.Post{}} =
               Test.Fixtures.Posts.status_post(:status)
               |> Core.Posts.create_post()
    end

    test "creates a blug post" do
      assert {:ok, %Schema.Post{}} =
               Test.Fixtures.Posts.blog_post(:blog)
               |> Core.Posts.create_post()
    end

    test "returns an error changeset for invalid attrs" do
      assert {:error, %Ecto.Changeset{} = changeset} = Core.Posts.create_post(%{})
      refute changeset.valid?
      assert "must have a kind" in errors_on(changeset).kind
      assert "must have a body" in errors_on(changeset).body

      assert {:error, %Ecto.Changeset{} = changeset} = Core.Posts.create_post(%{kind: :blog})
      refute changeset.valid?
      assert "must have a body" in errors_on(changeset).body
      assert "can't be blank" in errors_on(changeset).title
      assert "can't be blank" in errors_on(changeset).slug

      assert {:error, %Ecto.Changeset{} = changeset} =
               Core.Posts.create_post(%{
                 kind: :blog,
                 title: "Hello, World!",
                 slug: "hello-world!",
                 body: "This is a sample blog posts."
               })

      refute changeset.valid?
      assert "has invalid format" in errors_on(changeset).slug
    end
  end

  describe "update_post/2" do
    @tag blog: :to_update
    test "updates a blog post", %{blogs: %{to_update: post}} do
      assert {:ok, %Schema.Post{} = post} = Core.Posts.update_post(post, %{title: "A new title"})
      assert post.tid == "to_update"
      assert post.title == "A new title"
    end

    @tag status: :to_update
    test "updates a status post", %{statuses: %{to_update: post}} do
      assert {:ok, %Schema.Post{} = post} = Core.Posts.update_post(post, %{body: "Hello, World!"})
      assert post.tid == "to_update"
      assert post.body == "Hello, World!"
    end
  end

  describe "publish_post/1/2" do
    @describetag post: :to_publish

    test "publishes a post", %{posts: %{to_publish: to_publish}} do
      refute to_publish.published_at
      assert {:ok, published} = Core.Posts.publish_post(to_publish)
      assert published.tid == "to_publish"
      assert published.published_at
    end

    test "does not update an already published post", %{posts: %{to_publish: to_publish}} do
      assert {:ok, published} = Core.Posts.publish_post(to_publish)
      assert {:ok, published_again} = Core.Posts.publish_post(published)
      assert published.updated_at == published_again.updated_at
    end
  end

  describe "delete_post/1/2" do
    @describetag post: :to_delete

    test "soft deletes a post", %{posts: %{to_delete: post}} do
      refute post.deleted_at
      assert {:ok, post} = Core.Posts.delete_post(post)
      assert post.deleted_at
    end

    test "does not update an already deleted post", %{posts: %{to_delete: to_delete}} do
      assert {:ok, deleted} = Core.Posts.delete_post(to_delete)
      assert {:ok, deleted_again} = Core.Posts.delete_post(deleted)
      assert deleted.updated_at == deleted_again.updated_at
    end
  end

  describe "unpublish_post/1" do
    @describetag posts: [:published, :not_published]
    setup %{posts: %{published: published, not_published: not_published}} do
      {:ok, published} = Core.Posts.publish_post(published)

      [posts: %{published: published, not_published: not_published}]
    end

    test "unpublishes a post", %{posts: %{published: published}} do
      assert published.published_at
      assert {:ok, unpublished} = Core.Posts.unpublish_post(published)
      refute unpublished.published_at
    end

    test "does not update a post that isn't published", %{posts: %{not_published: not_published}} do
      refute not_published.published_at
      assert {:ok, not_updated} = Core.Posts.unpublish_post(not_published)
      assert not_published.updated_at == not_updated.updated_at
    end
  end

  describe "undelete_post/1" do
    @describetag posts: [:deleted, :not_deleted]
    setup %{posts: %{deleted: deleted, not_deleted: not_deleted}} do
      {:ok, deleted} = Core.Posts.delete_post(deleted)

      [posts: %{deleted: deleted, not_deleted: not_deleted}]
    end

    test "undeletees a post", %{posts: %{deleted: deleted}} do
      assert deleted.deleted_at
      assert {:ok, undeleted} = Core.Posts.undelete_post(deleted)
      refute undeleted.deleted_at
    end

    test "does not update a post that isn't deleted", %{posts: %{not_deleted: not_deleted}} do
      refute not_deleted.deleted_at
      assert {:ok, not_updated} = Core.Posts.undelete_post(not_deleted)
      assert not_deleted.updated_at == not_updated.updated_at
    end
  end
end