{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell   #-}

module Web.Payments.Cielo.Types where

import           Control.Exception
import           Control.Monad.Except
import           Control.Monad.Reader
import           Data.Aeson
import           Data.Aeson.Types
import qualified Data.ByteString.Lazy                as BL
import           Data.Convertible
import           Data.Default
import           Data.Monoid                         ((<>))
import           Data.Text                           (Text)
import           Network.HTTP.Client
import           Web.Payments.Cielo.Types.DeriveJSON

type MonadCielo m = ( MonadIO m
                    , MonadReader CieloConfig m
                    , MonadError CieloError m
                    )
type CieloM a = ReaderT CieloConfig (ExceptT CieloError IO) a

data CieloConfig = CieloConfig { cieloConfigMerchant    :: Merchant
                               , cieloConfigEnvironment :: Environment
                               }

data CieloError
    = CieloJSONError String SomeException BL.ByteString
    | CieloInvalidError String HttpException (Maybe Value)
    | CieloNotFoundError String HttpException
    | CieloHttpException String HttpException (Maybe Value)
  deriving(Show)

instance Exception CieloError where

data Merchant = Merchant { merchantId  :: Text
                         , merchantKey :: Text
                         }
  deriving(Read, Show, Eq)

data Environment = Environment { environmentApiUrl      :: Text
                               , environmentApiQueryUrl :: Text
                               }
  deriving(Read, Show, Eq)

instance Default Environment where
    def = sandboxEnv

productionEnv :: Environment
productionEnv = Environment "https://api.cieloecommerce.cielo.com.br"
                            "https://apiquery.cieloecommerce.cielo.com.br"

sandboxEnv :: Environment
sandboxEnv = Environment "https://apisandbox.cieloecommerce.cielo.com.br"
                         "https://apiquerysandbox.cieloecommerce.cielo.com.br"

data PaymentProvider = PaymentProviderBradesco
                     | PaymentProviderBancoDoBrasil
                     | PaymentProviderSimulado
  deriving(Read, Show, Eq)

deriveJSON ''PaymentProvider

data PaymentType = PaymentTypeCreditCard
                 | PaymentTypeDebitCard
                 | PaymentTypeElectronicTransfer
                 | PaymentTypeBoleto
  deriving(Read, Show, Eq)

instance Default PaymentType where
    def = PaymentTypeCreditCard

deriveJSON ''PaymentType

data Currency = CurrencyBRL
              | CurrencyUSD
              | CurrencyMXN
              | CurrencyCOP
              | CurrencyCLP
              | CurrencyARS
              | CurrencyPEN
              | CurrencyEUR
              | CurrencyPYN
              | CurrencyUYU
              | CurrencyVEB
              | CurrencyVEF
              | CurrencyGBP
  deriving(Read, Show, Eq)

instance Default Currency where
    def = CurrencyBRL

deriveJSON ''Currency

data Interval = IntervalMonthly
              | IntervalBimonthly
              | IntervalQuarterly
              | IntervalSemiAnnual
              | IntervalAnnual
  deriving(Read, Show, Eq)

instance Default Interval where
    def = IntervalMonthly

instance ToJSON Interval where
    toJSON IntervalMonthly = "Monthly"
    toJSON IntervalBimonthly = "Bimonthly"
    toJSON IntervalQuarterly = "Quarterly"
    toJSON IntervalSemiAnnual = "SemiAnnual"
    toJSON IntervalAnnual = "Annual"

instance FromJSON Interval where
    parseJSON (String m) = case m of
        "Monthly" -> return IntervalMonthly
        "Bimonthly" -> return IntervalBimonthly
        "Quarterly" -> return IntervalQuarterly
        "SemiAnnual" -> return IntervalSemiAnnual
        "Annual" -> return IntervalAnnual
        str -> fail ("Failed to parse Interval " <> convert str)
    parseJSON (Number m) = case m of
        1 -> return IntervalMonthly
        2 -> return IntervalBimonthly
        4 -> return IntervalQuarterly
        6 -> return IntervalSemiAnnual
        12 -> return IntervalAnnual
        n -> fail ("Failed to parse Interval " <> show n)
    parseJSON invalid = typeMismatch "Interval" invalid

data RecurrentPayment = RecurrentPayment { recurrentPaymentAuthorizeNow       :: Maybe Bool
                                         , recurrentPaymentEndDate            :: Maybe Text
                                         , recurrentPaymentStartDate          :: Maybe Text
                                         , recurrentPaymentInterval           :: Maybe Interval
                                         , recurrentPaymentRecurrentPaymentId :: Maybe Text
                                         }
  deriving(Read, Show, Eq)

instance Default RecurrentPayment where
    def = RecurrentPayment { recurrentPaymentAuthorizeNow = Just True
                           , recurrentPaymentEndDate = Nothing
                           , recurrentPaymentStartDate = Nothing
                           , recurrentPaymentInterval = Just IntervalMonthly
                           , recurrentPaymentRecurrentPaymentId = Nothing
                           }

deriveJSON ''RecurrentPayment

data Address = Address { addressStreet     :: Maybe Text
                       , addressNumber     :: Maybe Text
                       , addressComplement :: Maybe Text
                       , addressZipCode    :: Maybe Text
                       , addressCity       :: Maybe Text
                       , addressState      :: Maybe Text
                       , addressCountry    :: Maybe Text
                       }
  deriving(Read, Show, Eq)

instance Default Address where
    def = Address Nothing Nothing Nothing Nothing Nothing Nothing Nothing

deriveJSON ''Address

data CreditCard = CreditCard { creditCardCardNumber     :: Text
                             , creditCardHolder         :: Text
                             , creditCardExpirationDate :: Text
                             , creditCardSecurityCode   :: Maybe Text
                             , creditCardSaveCard       :: Maybe Bool
                             , creditCardBrand          :: Text
                             , creditCardCardToken      :: Maybe Text
                             }
  deriving(Read, Show, Eq)

instance Default CreditCard where
    def = CreditCard "" "" "" Nothing Nothing "" Nothing

deriveJSON ''CreditCard

data Customer = Customer { customerName            :: Text
                         , customerEmail           :: Maybe Text
                         , customerBirthDate       :: Maybe Text
                         , customerIdentity        :: Maybe Text
                         , customerIdentityType    :: Maybe Text
                         , customerAddress         :: Maybe Address
                         , customerDeliveryAddress :: Maybe Address
                         }
  deriving(Read, Show, Eq)

instance Default Customer where
    def = Customer "" Nothing Nothing Nothing Nothing Nothing Nothing

deriveJSON ''Customer

data Payment = Payment { paymentServiceTaxAmount    :: Maybe Int
                       , paymentInstallments        :: Maybe Int
                       , paymentInterest            :: Maybe Value
                       -- ^ Sometimes this is a string, sometimes it's an int... :P
                       , paymentCreditCard          :: Maybe CreditCard
                       , paymentType                :: Maybe PaymentType
                       , paymentAmount              :: Maybe Int
                       , paymentCapture             :: Maybe Bool
                       , paymentAuthenticate        :: Maybe Bool
                       , paymentRecurrent           :: Maybe Bool
                       , paymentRecurrentPayment    :: Maybe RecurrentPayment
                       , paymentTid                 :: Maybe Text
                       , paymentProofOfSale         :: Maybe Text
                       , paymentAuthorizationCode   :: Maybe Text
                       , paymentSoftDescriptor      :: Maybe Text
                       , paymentReturnUrl           :: Maybe Text
                       , paymentProvider            :: Maybe PaymentProvider
                       , paymentPaymentId           :: Maybe Text
                       , paymentReceivedDate        :: Maybe Text
                       , paymentCapturedAmount      :: Maybe Int
                       , paymentCapturedDate        :: Maybe Text
                       , paymentCurrency            :: Maybe Currency
                       , paymentCountry             :: Maybe Text
                       , paymentReturnCode          :: Maybe Text
                       , paymentReturnMessage       :: Maybe Text
                       , paymentStatus              :: Maybe Int
                       , paymentLinks               :: Maybe [Value]
                       , paymentExtraDataCollection :: Maybe [Value]
                       , paymentExpirationDate      :: Maybe Text
                       , paymentUrl                 :: Maybe Text
                       , paymentNumber              :: Maybe Text
                       , paymentBarCodeNumber       :: Maybe Text
                       , paymentDigitableLine       :: Maybe Text
                       , paymentAddress             :: Maybe Text
                       }
  deriving(Read, Show, Eq)

instance Default Payment where
    def = Payment { paymentServiceTaxAmount    = Just 0
                  , paymentInstallments        = Just 1
                  , paymentInterest            = Just Null
                  , paymentCapture             = Nothing
                  , paymentAuthenticate        = Nothing
                  , paymentRecurrent           = Nothing
                  , paymentRecurrentPayment    = Nothing
                  , paymentCreditCard          = Just def
                  , paymentTid                 = Nothing
                  , paymentProofOfSale         = Nothing
                  , paymentAuthorizationCode   = Nothing
                  , paymentSoftDescriptor      = Nothing
                  , paymentReturnUrl           = Nothing
                  , paymentProvider            = Nothing
                  , paymentPaymentId           = Nothing
                  , paymentType                = Just def
                  , paymentAmount              = Just 0
                  , paymentReceivedDate        = Nothing
                  , paymentCapturedAmount      = Nothing
                  , paymentCapturedDate        = Nothing
                  , paymentCurrency            = Nothing
                  , paymentCountry             = Nothing
                  , paymentReturnCode          = Nothing
                  , paymentReturnMessage       = Nothing
                  , paymentStatus              = Nothing
                  , paymentLinks               = Nothing
                  , paymentExtraDataCollection = Nothing
                  , paymentExpirationDate      = Nothing
                  , paymentUrl                 = Nothing
                  , paymentNumber              = Nothing
                  , paymentBarCodeNumber       = Nothing
                  , paymentDigitableLine       = Nothing
                  , paymentAddress             = Nothing
                  }

deriveJSON ''Payment

data Sale = Sale { saleMerchantOrderId :: Text
                 , saleCustomer        :: Customer
                 , salePayment         :: Payment
                 }
  deriving(Read, Show, Eq)

deriveJSON ''Sale

data SaleUpdate = SaleUpdate { saleUpdateStatus        :: Int
                             , saleUpdateReturnCode    :: Text
                             , saleUpdateReturnMessage :: Text
                             , saleUpdateLinks         :: [Value]
                             }
  deriving(Read, Show, Eq)

deriveJSON ''SaleUpdate

data RecurrentPaymentQuery
    = RecurrentPaymentQuery { recurrentPaymentQueryRecurrentPayment :: RecurrentPayment
                            , recurrentPaymentQueryCustomer         :: Customer
                            }
  deriving(Read, Show, Eq)

deriveJSON ''RecurrentPaymentQuery

data SalesByMerchantOrderQuery
    = SalesByMerchantOrderQuery { salesByMerchantOrderQueryPayments :: [Payment]
                                }
  deriving(Read, Show, Eq)

deriveJSON ''SalesByMerchantOrderQuery