# LANGUAGE TypeFamilies #
# LANGUAGE FlexibleContexts #
{-# LANGUAGE DeriveDataTypeable #-}
# LANGUAGE DataKinds #
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module SpecHelper
  ( -- * Expectations
    Fold
  , shouldHaveResponse
  , shouldHaveJson
  , shouldHaveJson_
  , shouldHaveXml
  , shouldHaveXml_
    -- * public data
  , publicly
  , publicKey
    -- * private data
    -- $awful
  , privately
  , privateAPIKey
  , privateSessionKey
  , privateSecret
    -- $really-awful
  , man
  ) where

import           Control.Exception (Exception, throwIO)
import           Control.Lens
import           Data.Aeson (Value)
import           Data.Aeson.Lens
import           Data.Text (Text, pack)
import           Data.Typeable (Typeable)
import qualified Network.HTTP.Client as Http
import qualified Network.HTTP.Client.TLS as Http
import           Lastfm
import           System.Environment
import           System.IO.Unsafe (unsafePerformIO)
import           Test.Hspec
import           Test.HUnit (assertFailure)
import           Text.Printf
import           Text.Xml.Lens
import           Unsafe.Coerce (unsafeCoerce)

infixl 1 `shouldHaveJson`, `shouldHaveXml`


-- | Inspect 'Response' with 'Query'
shouldHaveResponse :: (Show r, Supported f r) => Request f Ready -> Fold r a -> Expectation
shouldHaveResponse q l = do
  r <- lastfm man q
  case preview (_Right.l) r of
    Just _  -> return ()
    Nothing -> assertFailure (printf "Query failed on %s" (show r))

shouldHaveJson :: Request 'JSON Ready -> Fold Value a -> Expectation
shouldHaveJson = shouldHaveResponse

shouldHaveXml :: Request 'XML Ready -> Fold Document a -> Expectation
shouldHaveXml = shouldHaveResponse

-- | Check success stuff for POST requests
shouldHaveJson_ :: Request 'JSON Ready -> Expectation
shouldHaveJson_ l = shouldHaveResponse l (key "status".only "ok")

shouldHaveXml_ :: Request 'XML Ready -> Expectation
shouldHaveXml_ l = shouldHaveResponse l (root.attributed (ix "status".only "ok"))

-- | Make a request using public API key
publicly :: Request f (APIKey -> Ready) -> Request f Ready
publicly r = r <*> publicKey

-- | API Key used for requests that do not require authentification
publicKey :: Request f APIKey
publicKey = apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318"

-- | Environment is missing a variable
data EnvironmentMissing = EnvironmentMissing String deriving (Show, Eq, Typeable)

instance Exception EnvironmentMissing

-- $awful

-- | Make a request signed by a secret using private API and session keys
privately :: Request f (APIKey -> SessionKey -> Sign) -> Request f Ready
privately r = sign privateSecret $ r <*> privateAPIKey <*> privateSessionKey

privateAPIKey :: Request f APIKey
privateAPIKey = apiKey (liblastfmEnv "HASKELL_LIBLASTFM_APIKEY")

privateSessionKey :: Request f SessionKey
privateSessionKey = sessionKey (liblastfmEnv "HASKELL_LIBLASTFM_SESSIONKEY")

privateSecret :: Secret
privateSecret = Secret (liblastfmEnv "HASKELL_LIBLASTFM_SECRET")

-- | Get an environment variable or throw a 'EnvironmentMissing' exception
liblastfmEnv :: String -> Text
liblastfmEnv var = unsafePerformIO $ do
  mv <- lookupEnv var
  case mv of
    Just v  -> return (pack v)
    Nothing -> throwIO (EnvironmentMissing var)

-- $really-awful

man :: Connection
man = unsafeCoerce (unsafePerformIO (Http.newManager Http.tlsManagerSettings))

# LANGUAGE DeriveDataTypeable #
# LANGUAGE OverloadedStrings #
# LANGUAGE RankNTypes #
 * Expectations
 * public data
 * private data
 $awful
 $really-awful
 | Inspect 'Response' with 'Query'
 | Check success stuff for POST requests
 | Make a request using public API key
 | API Key used for requests that do not require authentification
 | Environment is missing a variable
 $awful
 | Make a request signed by a secret using private API and session keys
 | Get an environment variable or throw a 'EnvironmentMissing' exception
 $really-awful
# LANGUAGE TypeFamilies #
# LANGUAGE FlexibleContexts #
# LANGUAGE DataKinds #
module SpecHelper
    Fold
  , shouldHaveResponse
  , shouldHaveJson
  , shouldHaveJson_
  , shouldHaveXml
  , shouldHaveXml_
  , publicly
  , publicKey
  , privately
  , privateAPIKey
  , privateSessionKey
  , privateSecret
  , man
  ) where

import           Control.Exception (Exception, throwIO)
import           Control.Lens
import           Data.Aeson (Value)
import           Data.Aeson.Lens
import           Data.Text (Text, pack)
import           Data.Typeable (Typeable)
import qualified Network.HTTP.Client as Http
import qualified Network.HTTP.Client.TLS as Http
import           Lastfm
import           System.Environment
import           System.IO.Unsafe (unsafePerformIO)
import           Test.Hspec
import           Test.HUnit (assertFailure)
import           Text.Printf
import           Text.Xml.Lens
import           Unsafe.Coerce (unsafeCoerce)

infixl 1 `shouldHaveJson`, `shouldHaveXml`


shouldHaveResponse :: (Show r, Supported f r) => Request f Ready -> Fold r a -> Expectation
shouldHaveResponse q l = do
  r <- lastfm man q
  case preview (_Right.l) r of
    Just _  -> return ()
    Nothing -> assertFailure (printf "Query failed on %s" (show r))

shouldHaveJson :: Request 'JSON Ready -> Fold Value a -> Expectation
shouldHaveJson = shouldHaveResponse

shouldHaveXml :: Request 'XML Ready -> Fold Document a -> Expectation
shouldHaveXml = shouldHaveResponse

shouldHaveJson_ :: Request 'JSON Ready -> Expectation
shouldHaveJson_ l = shouldHaveResponse l (key "status".only "ok")

shouldHaveXml_ :: Request 'XML Ready -> Expectation
shouldHaveXml_ l = shouldHaveResponse l (root.attributed (ix "status".only "ok"))

publicly :: Request f (APIKey -> Ready) -> Request f Ready
publicly r = r <*> publicKey

publicKey :: Request f APIKey
publicKey = apiKey "234fc6e0f41f6ef99b7bd62ebaf8d318"

data EnvironmentMissing = EnvironmentMissing String deriving (Show, Eq, Typeable)

instance Exception EnvironmentMissing


privately :: Request f (APIKey -> SessionKey -> Sign) -> Request f Ready
privately r = sign privateSecret $ r <*> privateAPIKey <*> privateSessionKey

privateAPIKey :: Request f APIKey
privateAPIKey = apiKey (liblastfmEnv "HASKELL_LIBLASTFM_APIKEY")

privateSessionKey :: Request f SessionKey
privateSessionKey = sessionKey (liblastfmEnv "HASKELL_LIBLASTFM_SESSIONKEY")

privateSecret :: Secret
privateSecret = Secret (liblastfmEnv "HASKELL_LIBLASTFM_SECRET")

liblastfmEnv :: String -> Text
liblastfmEnv var = unsafePerformIO $ do
  mv <- lookupEnv var
  case mv of
    Just v  -> return (pack v)
    Nothing -> throwIO (EnvironmentMissing var)


man :: Connection
man = unsafeCoerce (unsafePerformIO (Http.newManager Http.tlsManagerSettings))
