{-# LANGUAGE DataKinds         #-}
{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeOperators     #-}

module Main where

import           Control.Monad             (zipWithM_)
import qualified Data.Algorithm.Diff       as Diff
import qualified Data.Algorithm.DiffOutput as Diff
import           Data.Monoid               ((<>))
import           Data.Text                 (Text)
import qualified Data.Text                 as T
import qualified Data.Text.IO              as T
import           Servant.API               ((:>), Get, JSON)
import           Servant.Elm
import           Test.Hspec                (Spec, describe, hspec, it)
import           Test.HUnit                (Assertion, assertEqual)

import           Common                    (testApi)
import           PolymorphicData           (SomeRecord(..), PolymorphicData(..))


main :: IO ()
main = hspec spec

spec :: Test.Hspec.Spec
spec =
    describe "encoding a simple api" $
        do it "does it" $
               do expected <-
                      mapM
                          (\(fpath,header) -> do
                               source <- T.readFile fpath
                               return (fpath, header, source))
                          [ ( "test/elm-sources/getOneSource.elm"
                            , "module GetOneSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Json.Decode exposing (..)\n" <>
                              "import Url.Builder\n\n\n"
                            )
                          , ( "test/elm-sources/postTwoSource.elm"
                            , "module PostTwoSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Json.Decode exposing (..)\n" <>
                              "import Json.Encode\n" <>
                              "import Url.Builder\n\n\n"
                            )
                          , ( "test/elm-sources/getBooksByIdSource.elm"
                            , "module GetBooksByIdSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode\n" <>
                              "\n" <>
                              "type Book = Book\n" <>
                              "jsonDecBook : Json.Decode.Decoder Book\n" <>
                              "jsonDecBook = Debug.todo \"\"\n\n"
                            )
                          , ( "test/elm-sources/getBooksByTitleSource.elm"
                            , "module GetBooksByTitleSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode as J\n\n" <>
                              "type alias Book = {}\n" <>
                              "jsonDecBook = J.succeed {}\n\n"
                            )
                          , ( "test/elm-sources/getBooksSource.elm"
                            , "module GetBooksSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Json.Decode exposing (..)\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode as J\n\n" <>
                              "type alias Book = {}\n\n" <>
                              "jsonDecBook = J.succeed {}\n\n"
                            )
                          , ( "test/elm-sources/postBooksSource.elm"
                            , "module PostBooksSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Encode as Enc\n\n" <>
                              "type alias Book = {}\n" <>
                              "jsonEncBook = \\b -> Enc.object []\n\n"
                            )
                          , ( "test/elm-sources/getNothingSource.elm"
                            , "module GetNothingSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n\n\n"
                            )
                          , ( "test/elm-sources/putNothingSource.elm"
                            , "module PutNothingSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n\n\n"
                            )
                          , ( "test/elm-sources/getWithaheaderSource.elm"
                            , "module GetWithAHeaderSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode exposing (..)\n\n\n")
                          , ( "test/elm-sources/getWitharesponseheaderSource.elm"
                            , "module GetWithAResponseHeaderSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode exposing (..)\n\n\n")]
                  let generated = filter (not . T.null) (generateElmForAPI testApi)
                  generated `itemsShouldBe` expected
           it "with dynamic URLs" $
               do expected <-
                      mapM
                          (\(fpath,header) -> do
                               source <- T.readFile fpath
                               return (fpath, header, source))
                          [ ( "test/elm-sources/getOneWithDynamicUrlSource.elm"
                            , "module GetOneWithDynamicUrlSource exposing (..)\n\n" <>
                              "import Http\n" <>
                              "import Url.Builder\n" <>
                              "import Json.Decode exposing (..)\n\n\n")]
                  let generated =
                          map
                              (<> "\n")
                              (generateElmForAPIWith
                                   (defElmOptions
                                    { urlPrefix = Dynamic
                                    })
                                   (Proxy :: Proxy ("one" :> Get '[JSON] Int)))
                  generated `itemsShouldBe` expected
           it "works with polymorphic data" $
               do expected <-
                     mapM
                         (\(fpath, header) -> do
                           source <- T.readFile fpath
                           return (fpath, header, source))
                         [ ( "test/elm-sources/getPolymorphicData.elm"
                           , "module GetPolymorphicData exposing (..)\n\n" <>
                             "import Http\n" <>
                             "import Json.Decode exposing (..)\n" <>
                             "import Url.Builder\n\n" <>
                             "type PolymorphicData a b = PolymorphicData a b\n" <>
                             "type SomeRecord = SomeRecord { recordId : Int, recordname : String }\n\n" <>
                             "jsonDecPolymorphicData : Json.Decode.Decoder a -> Json.Decode.Decoder b -> Json.Decode.Decoder (PolymorphicData a b)\n"<>
                             "jsonDecPolymorphicData _ _ = Debug.todo \"finish\"\n\n" <>
                             "jsonDecSomeRecord : Json.Decode.Decoder SomeRecord\n"<>
                             "jsonDecSomeRecord = Debug.todo \"finish\"\n\n\n")]
                  let generated =
                        map
                          (<> "\n")
                          (generateElmForAPIWith
                               defElmOptions
                               (Proxy :: Proxy ( "polymorphicData" :> Get '[JSON] (PolymorphicData [String] SomeRecord))))
                  generated `itemsShouldBe` expected

itemsShouldBe :: [Text] -> [(String, Text, Text)] -> IO ()
itemsShouldBe actual expected =
    zipWithM_
        shouldBeDiff
        (actual ++ replicate (length expected - length actual) mempty)
        (expected ++ replicate (length actual - length expected) mempty)

shouldBeDiff :: Text -> (String, Text, Text) -> Assertion
shouldBeDiff a (fpath,header,b) =
    assertEqual
        ("< generated\n" <> "> " <> fpath <> "\n" <>
         Diff.ppDiff
             (Diff.getGroupedDiff
                  (lines (T.unpack actual))
                  (lines (T.unpack expected))))
         expected actual
    where
      actual = T.strip $ header <> a
      expected = T.strip b
