module Spec.Article (spec) where

import ClassyPrelude
import Test.Hspec
import Feature.Comment.Types
import Feature.User.Types
import Feature.Article.Types
import Feature.Auth.Types
import Feature.Common.Types
import qualified Misc.Client as RW
import Text.StringRandom

import Spec.Common

spec :: Spec
spec = do
  articleSpec
  favoriteSpec
  tagSpec


articleSpec :: Spec
articleSpec =
  describe "articles" $ do

    describe "create articles" $ do

      it "should require valid token" $
        runClient (RW.createArticle "invalidToken" $ CreateArticle "" "" "" [])
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")

      it "should create article successfully" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        Right article' <- runClient $ RW.getArticle (Just $ userToken user) (articleSlug article)
        article `shouldBe` article'

    describe "update articles" $ do

      it "should require valid token" $
        runClient (RW.updateArticle "invalidToken" "invalidSlug" $ UpdateArticle Nothing Nothing Nothing)
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")

      it "should reject updating non-existent articles" $ do
        Right user <- registerRandomUser
        runClient (RW.updateArticle (userToken user) "invalidSlug" $ UpdateArticle Nothing Nothing Nothing)
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound "invalidSlug")

      it "should reject updating articles who is not owned by the user" $ do
        Right user1 <- registerRandomUser
        Right user2 <- registerRandomUser
        Right article <- createRandomArticle user2 []
        runClient (RW.updateArticle (userToken user1) (articleSlug article) $ UpdateArticle Nothing Nothing Nothing)
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotAllowed (articleSlug article))

      it "should update title successfully" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        newTitle <- stringRandomIO "[a-zA-Z0-9 ]{5}"
        Right article' <- runClient (RW.updateArticle (userToken user) (articleSlug article) $ UpdateArticle (Just newTitle) Nothing Nothing)
        let expected = article { articleSlug = articleSlug article', articleTitle = newTitle, articleUpdatedAt = articleUpdatedAt article' }
        article' `shouldBe` expected
        (articleUpdatedAt article < articleUpdatedAt article') `shouldBe` True

      it "should update desc, body successfully" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        newDesc <- stringRandomIO "[a-zA-Z0-9 ]{5}"
        newBody <- stringRandomIO "[a-zA-Z0-9 ]{5}"
        Right article' <- runClient (RW.updateArticle (userToken user) (articleSlug article) $ UpdateArticle Nothing (Just newDesc) (Just newBody))
        let expected = article { articleSlug = articleSlug article', articleDescription = newDesc, articleBody = newBody, articleUpdatedAt = articleUpdatedAt article' }
        article' `shouldBe` expected
        (articleUpdatedAt article < articleUpdatedAt article') `shouldBe` True

    describe "delete articles" $ do

      it "should require valid token" $
        runClient (RW.deleteArticle "invalidToken" "invalidSlug")
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")

      it "should reject deleting non-existent article" $ do
        Right user <- registerRandomUser
        runClient (RW.deleteArticle (userToken user) "invalidSlug")
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound "invalidSlug")

      it "should reject deleting articles who is not owned by the user" $ do
        Right user1 <- registerRandomUser
        Right user2 <- registerRandomUser
        Right article <- createRandomArticle user2 []
        runClient (RW.deleteArticle (userToken user1) (articleSlug article))
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotAllowed (articleSlug article))

      it "should delete articles successfully" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        runClient (RW.deleteArticle (userToken user) (articleSlug article))
          `shouldReturn` Right ()
        runClient (RW.getArticle (Just $ userToken user) (articleSlug article))
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound (articleSlug article))

    describe "get articles" $ do

      it "should able to filter by tag and author" $ do
        Right user1 <- registerRandomUser
        Right user2 <- registerRandomUser
        tags@[tag] <- randomTags 1
        Right article1 <- createRandomArticle user2 tags
        Right article2 <- createRandomArticle user2 tags
        Right article3 <- createRandomArticle user1 tags
        let filter = ArticleFilter (Just tag) (Just $ userUsername user2) Nothing
        let pagination = Pagination 100 0
        runClient (RW.getArticles Nothing filter pagination)
          `shouldReturn` Right [article2, article1]

      it "should able to filter by favoritedBy" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        let token = userToken user
        let slug = articleSlug article
        runClient (RW.favoriteArticle token slug)
        let expected = article { articleFavorited = True, articleFavoritesCount = 1 }
        let filter = ArticleFilter Nothing Nothing (Just $ userUsername user)
        let pagination = Pagination 100 0
        runClient (RW.getArticles (Just token) filter pagination)
          `shouldReturn` Right [expected]

    describe "get feed" $ do

      it "should require valid token" $
        runClient (RW.getFeed "invalidToken" (Pagination 100 0))
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")

      it "should return empty if the user doesn't follow anyone" $ do
        Right user <- registerRandomUser
        runClient (RW.getFeed (userToken user) (Pagination 100 0))
          `shouldReturn` Right []

      it "should get feed successfully" $ do
        Right user1 <- registerRandomUser
        Right user2 <- registerRandomUser
        Right article <- createRandomArticle user2 []
        runClient (RW.followUser (userToken user1) (userUsername user2))
        let expected = article { articleAuthor = (articleAuthor article) { profileFollowing = True } }
        runClient (RW.getFeed (userToken user1) (Pagination 100 0))
          `shouldReturn` Right [expected]

    describe "get article" $

      it "should return err if the article is not found" $
        runClient (RW.getArticle Nothing "invalidSlug")
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound "invalidSlug")

    describe "pagination" $ do

      it "should return first page for < 0 pagination offset" $ do
        Right user <- registerRandomUser
        createRandomArticle user []
        let filter = ArticleFilter Nothing (Just $ userUsername user) Nothing
        let pagination = Pagination 100 (-1)
        actual <- runClient (RW.getArticles Nothing filter pagination)
        expected <- runClient (RW.getArticles Nothing filter $ Pagination 100 0)
        actual `shouldBe` expected

      it "should return empty for > out of bounds pagination offset" $ do
        Right user <- registerRandomUser
        createRandomArticle user []
        let filter = ArticleFilter Nothing Nothing Nothing
        let pagination = Pagination 100 1000000 -- arbitrarily big offset
        runClient (RW.getArticles Nothing filter pagination)
          `shouldReturn` Right []

      it "should return empty for <= 0 pagination limit" $ do
        Right user <- registerRandomUser
        createRandomArticle user []
        let filter = ArticleFilter Nothing Nothing Nothing
        let pagination = Pagination (-1) 0
        runClient (RW.getArticles Nothing filter pagination)
          `shouldReturn` Right []

      it "should correctly break the pages based on limit" $ do
        Right user <- registerRandomUser
        tags@[tag] <- randomTags 1 
        Right article1 <- createRandomArticle user tags
        Right article2 <- createRandomArticle user tags
        let filter = ArticleFilter (Just tag) Nothing Nothing
        runClient (RW.getArticles Nothing filter (Pagination 1 0))
          `shouldReturn` Right [article2]
        runClient (RW.getArticles Nothing filter (Pagination 1 1))
          `shouldReturn` Right [article1]

          
favoriteSpec :: Spec
favoriteSpec =
  describe "favorites" $ do

    describe "favorite" $ do

      it "should require valid token" $
        runClient (RW.favoriteArticle "invalidToken" "invalidSlug")
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")
          
      it "should throw err if trying to favorite non-existent article" $ do
        Right user <- registerRandomUser
        let token = userToken user
        runClient (RW.favoriteArticle token "invalidSlug")
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound "invalidSlug")

      it "should favorite successfully & idempotently" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        let token = userToken user
        let slug = articleSlug article
        let expected = article { articleFavorited = True, articleFavoritesCount = 1 }
        runClient (RW.favoriteArticle token slug)
          `shouldReturn` Right expected
        runClient (RW.favoriteArticle token slug)
          `shouldReturn` Right expected

    describe "unfavorite" $ do

      it "should require valid token" $
        runClient (RW.favoriteArticle "invalidToken" "invalidSlug")
          `shouldReturn` Left (RW.ErrUnauthorized $ TokenErrorMalformed "BadCrypto")
          
      it "should throw err if trying to unfavorite non-existent article" $ do
        Right user <- registerRandomUser
        let token = userToken user
        runClient (RW.unfavoriteArticle token "invalidSlug")
          `shouldReturn` Left (RW.ErrApp $ ArticleErrorNotFound "invalidSlug")

      it "should unfavorite successfully & idempotently" $ do
        Right user <- registerRandomUser
        Right article <- createRandomArticle user []
        let token = userToken user
        let slug = articleSlug article
        runClient (RW.favoriteArticle token slug)
        runClient (RW.unfavoriteArticle token slug)
          `shouldReturn` Right article
        runClient (RW.unfavoriteArticle token slug)
          `shouldReturn` Right article


tagSpec :: Spec
tagSpec =
  describe "tags" $
    it "should get tags successfully" $ do
      Right user <- registerRandomUser
      tags <- forM [1..10] $ const (stringRandomIO "[a-zA-Z]{10}")
      createRandomArticle user tags
      Right allTags <- runClient RW.getTags
      (setFromList tags `union` allTags) `shouldBe` allTags