{-# LANGUAGE TypeOperators       #-}
{-# LANGUAGE TypeFamilies        #-}
{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE ExplicitForAll      #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE OverloadedLabels    #-}
{-# LANGUAGE FlexibleContexts    #-}

import Test.Hspec
import Test.QuickCheck.Monadic
import Test.QuickCheck (property)
import Servant.Server
import Servant.API
import Servant.API.NamedArgs
import Servant.Client.NamedArgs
import Servant.Server.NamedArgs
import Servant.Client
import Data.Proxy
import Data.Functor.Identity
import Named
import Named.Internal
import Data.Function ((&))
import Network.HTTP.Client (Manager(..), newManager, defaultManagerSettings)
import Control.Concurrent.Async
import qualified Network.Wai.Handler.Warp as W

type All = [ NameCaptures
           , NameCaptureAlls
           , NameFlags
           , NameParams
           , NameMultiParams
           , NameHeaders
           ]

type CaptureEndpoint = "capture" :> Capture "x" Int :> Get '[JSON] Int
type CaptureAllEndpoint = "captureAll" :> CaptureAll "xs" Int :> Get '[JSON] [Int]
type FlagEndpoint = "flag" :> QueryFlag "f" :> Get '[JSON] Bool
type ReqParamEndpoint = "requiredParam" :>  QueryParam' [Required, Strict] "r" Int :> Get '[JSON] Int
type OpParamEndpoint = "optionalParam" :>  QueryParam' [Optional, Strict] "o" Int :> Get '[JSON] Int
type ParamsEndpoint = "params" :>  QueryParams "ps" Int :> Get '[JSON] [Int]
type ReqHeaderEndpoint = "requiredHeader" :> Header' [Required, Strict] "rh" Int :> Get '[JSON] Int
type OpHeaderEndpoint = "optionalHeader" :> Header' [Optional, Strict] "oh" Int :> Get '[JSON] Int
                    
type TestApi =    CaptureEndpoint
             :<|> CaptureAllEndpoint
             :<|> FlagEndpoint
             :<|> ReqParamEndpoint
             :<|> OpParamEndpoint
             :<|> ParamsEndpoint
             :<|> ReqHeaderEndpoint
             :<|> OpHeaderEndpoint

unnamedServer :: Server TestApi
unnamedServer =    pure
              :<|> pure
              :<|> pure
              :<|> pure
              :<|> pure . def 19
              :<|> pure
              :<|> pure
              :<|> pure . def 19

namedServer :: Server (Transform All TestApi)
namedServer =    pureI
            :<|> pureI
            :<|> pureI
            :<|> pureI
            :<|> pureM
            :<|> pureI
            :<|> pureI
            :<|> pureM
  where
    pureI (Arg v) = pure v
    pureM (ArgF mv) = maybe (pure 19) pure mv

unnamedApp = serve (Proxy @TestApi) unnamedServer
namedApp = serve (Proxy @(Transform All TestApi)) namedServer

uCapture
  :<|> uCaptureAll
  :<|> uFlag
  :<|> uRParam
  :<|> uOParam
  :<|> uParams
  :<|> uRHeader
  :<|> uOHeader = client (Proxy @TestApi)

nCapture
  :<|> nCaptureAll
  :<|> nFlag
  :<|> nRParam
  :<|> nOParam
  :<|> nParams
  :<|> nRHeader
  :<|> nOHeader = client (Proxy @(Transform All TestApi))

clientServerEq
  :: (Eq r)
  => Manager
  -> BaseUrl
  -> BaseUrl
  -> f a
  -> (f a -> ClientM r)
  -> (f a -> ClientM r)
  -> IO Bool
clientServerEq man ub nb val uf nf
    =       (runUs uf') `meq` (runUs nf')
    `mand`  (runNs uf') `meq` (runUs nf')
    `mand`  (runNs uf') `meq` (runNs nf')
    `mand`  (runUs uf') `meq` (runNs nf')
  where
    unnamedServer = mkClientEnv man ub
    namedServer   = mkClientEnv man nb
    runUs c = runClientM c unnamedServer
    runNs c = runClientM c namedServer
    uf' = uf val
    nf' = nf val
    l `meq` r = (==) <$> l <*> r
    l `mand` r = (&&) <$> l <*> r
    infix 4 `meq`
    infixr 3 `mand`

withF :: forall l p f a fn fn'. (p ~ NamedF f a l, WithParam p fn fn')
      => f a -> fn -> fn'
withF p fn = with (Param $ ArgF @_ @_ @l p) fn

def :: a -> Maybe a -> a
def a Nothing  = a
def _ (Just b) = b

-- we make sure that all permutations of the named or unnamed server being
-- queried by the named or unnamed clients return the same
main :: IO ()
main = do
    let uh = 11008
        nh = 11009
    man <- newManager defaultManagerSettings
    us <- async $ W.run uh unnamedApp
    ns <- async $ W.run nh namedApp
    let
      ub = BaseUrl Http "localhost" uh ""
      nb = BaseUrl Http "localhost" nh ""
      doComp :: (Eq r)
             => f a
             -> (f a -> ClientM r)
             -> (f a -> ClientM r)
             -> IO Bool
      doComp = clientServerEq man ub nb
      ioprop = monadicIO . run 
    hspec $ do
      describe "Named and unnamed equivalency (client/server)" $ do
        it "Capture and NamedCapture are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uCapture . runIdentity) (nCapture . ArgF)
        it "CaptureAll and NamedCaptureAll are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uCaptureAll . def []) (nCaptureAll . ArgF)
        it "QueryFlag and NamedFlag are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uFlag . def False) (nFlag . ArgF)
        it "Required QueryParam and NamedParam are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uRParam . runIdentity) (nRParam . ArgF)
        it "Optional QueryParam and NamedParam are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uOParam) (nOParam . ArgF)
        it "Required QueryHeader and NamedHeader are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uRHeader . runIdentity) (nRHeader . ArgF)
        it "Optional QueryHeader and NamedHeader are equivalent" $ do
          property $ \x -> ioprop $ doComp x (uOHeader) (nOHeader . ArgF)
    cancel us
    cancel ns