{-# LANGUAGE OverloadedStrings #-}
-------------------------------------------
-- |
-- Module      : Web.Stripe.Card
-- Copyright   : (c) David Johnson, 2014
-- Maintainer  : djohnson.m@gmail.com
-- Stability   : experimental
-- Portability : POSIX
--
-- < https:/\/\stripe.com/docs/api#cards >
--
-- @
-- import Web.Stripe         
-- import Web.Stripe.Customer 
-- import Web.Stripe.Card
--
-- main :: IO ()
-- main = do
--   let config = SecretKey "secret_key"
--       credit = CardNumber "4242424242424242"
--       em  = ExpMonth 12
--       ey  = ExpYear 2015
--       cvc = CVC "123"
--   result <- stripe config $ do
--          Customer { customerId = cid } <- createEmptyCustomer
--          card <- createCustomerCard cid credit em ey cvc
--          return card
--   case result of
--     Right card -> print card
--     Left  stripeError -> print stripeError
-- @
module Web.Stripe.Card
    ( -- * API
      -- ** Customers
      -- *** Create Customer Card
      createCustomerCard
    , createCustomerCardByToken
      -- *** Get Customer Card(s)
    , getCustomerCard
    , getCustomerCardExpandable
    , getCustomerCards
    , getCustomerCardsExpandable
      -- *** Update Customer Card
    , updateCustomerCard
      -- *** Delete Card
    , deleteCustomerCard
      -- ** Recipients
      -- *** Create Recipient Card
    , createRecipientCard
    , createRecipientCardByToken
      -- *** Get Recipient Card(s)
    , getRecipientCard
    , getRecipientCardExpandable
    , getRecipientCards
    , getRecipientCardsExpandable
      -- *** Updated Recipient Card
    , updateRecipientCard
      -- *** Delete Recipient Card
    , deleteRecipientCard
      -- * Types
    , Brand           (..)
    , Card            (..)
    , RecipientCard   (..)
    , CardId          (..)
    , RecipientCardId (..)
    , CardNumber      (..)
    , ExpMonth        (..)
    , ExpYear         (..)
    , CVC             (..)
    , Name
    , AddressLine1    (..)
    , AddressLine2    (..)
    , AddressCity     (..)
    , AddressCountry  (..)
    , AddressState    (..)
    , AddressZip      (..)
    , RecipientId     (..)
    ) where

import           Control.Applicative        ((<$>))
import           Data.Aeson                 (FromJSON)
import           Web.Stripe.Client.Internal
import           Web.Stripe.Types
import           Web.Stripe.Types.Util 

------------------------------------------------------------------------------
-- | Base request function for `Card` creation, good for making custom create `Card` functions
createCardBase
  :: FromJSON a
  => URL                  -- ^ The `Recipient` or `Customer` on which to make the `Card` value can be "recipients" or "customers" only
  -> ID                   -- ^ The `RecipientId` or `CustomerId` for which the `Card` can be made
  -> Maybe TokenId        -- ^ The `TokenId` to be used for `Card` creation
  -> Maybe CardNumber     -- ^ `CardNumber` for `Card` creation
  -> Maybe ExpMonth       -- ^ Expiration Month for `Card` creation
  -> Maybe ExpYear        -- ^ Expiration Year for `Card` creation
  -> Maybe CVC            -- ^ CVC for `Card` creation
  -> Maybe Name           -- ^ Name of `Recipient` or `Customer` to be used for `Card`
  -> Maybe AddressCity    -- ^ City associated with `Card`
  -> Maybe AddressCountry -- ^ Country associated with `Card`
  -> Maybe AddressLine1   -- ^ Address Line 1 associated with `Card`
  -> Maybe AddressLine2   -- ^ Address Line 2 associated with `Card`
  -> Maybe AddressState   -- ^ Address State 2 associated with `Card`
  -> Maybe AddressZip     -- ^ Address Zip associated with `Card`
  -> Stripe a
createCardBase
    requestType
    requestId
    tokenid
    cardNumber
    expMonth
    expYear
    cvc
    name
    addressCity
    addressCountry
    addressLine1
    addressLine2
    addressState
    addressZip  = callAPI request
  where request = StripeRequest POST url params
        url     = requestType </> requestId </> "cards"
        params  = getParams [
                     ("card", (\(TokenId x) -> x) <$> tokenid)
                   , ("card[number]", (\(CardNumber x) -> x) <$> cardNumber)
                   , ("card[exp_month]", (\(ExpMonth x) -> toText x) <$> expMonth)
                   , ("card[exp_year]", (\(ExpYear x) -> toText x) <$> expYear)
                   , ("card[cvc]", (\(CVC x) -> x) <$> cvc)
                   , ("name", name)
                   , ("address_city", (\(AddressCity x) -> x) <$> addressCity)
                   , ("address_country", (\(AddressCountry x) -> x) <$> addressCountry)
                   , ("address_line1", (\(AddressLine1 x) -> x) <$> addressLine1 )
                   , ("address_line2", (\(AddressLine2 x) -> x) <$> addressLine2 )
                   , ("address_state", (\(AddressState x) -> x) <$> addressState )
                   , ("address_zip", (\(AddressZip x) -> x) <$> addressZip )
                  ]

------------------------------------------------------------------------------
-- | Create a `Customer` card using a `Token`
createCustomerCardByToken
    :: CustomerId -- ^ The Customer to which the card will be added
    -> TokenId    -- ^ The Token representative of the card
    -> Stripe Card
createCustomerCardByToken
    customerid
    tokenid = createCardBase "customers" (getCustomerId customerid) (Just tokenid)
              Nothing Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing 

------------------------------------------------------------------------------
-- | Create a `Recipient` card using a `Token`
createRecipientCardByToken
    :: RecipientId -- ^ The Customer to which the card will be added
    -> TokenId     -- ^ The Token representative of the card
    -> Stripe RecipientCard
createRecipientCardByToken
    recipientid
    tokenid = createCardBase "recipients" (getRecipientId recipientid) (Just tokenid)
              Nothing Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing 

------------------------------------------------------------------------------
-- | Add a `Card` on a `Customer`
createCustomerCard
    :: CustomerId -- ^ `Customer` to which the card will be added
    -> CardNumber -- ^ `Card` digits
    -> ExpMonth   -- ^ `Card` expiration month
    -> ExpYear    -- ^ `Card` expiration year
    -> CVC        -- ^ `Card` cvc number
    -> Stripe Card
createCustomerCard
    customerid
    cardNumber
    expMonth
    expYear
    cvc = createCardBase "customers" (getCustomerId customerid) Nothing
              (Just cardNumber) (Just expMonth) (Just expYear)
              (Just cvc) Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing 

------------------------------------------------------------------------------
-- | Create a `Recipient` `Card` by `CardNumber`
createRecipientCard
    :: RecipientId -- ^ `Recipient` to which the card will be added
    -> CardNumber  -- ^ `Card` digits
    -> ExpMonth    -- ^ `Card` expiration month
    -> ExpYear     -- ^ `Card` expiration year
    -> CVC         -- ^ `Card` cvc number
    -> Stripe RecipientCard
createRecipientCard
    recipientid
    cardNumber
    expMonth
    expYear
    cvc = createCardBase "recipients" (getRecipientId recipientid) Nothing
              (Just cardNumber) (Just expMonth) (Just expYear)
              (Just cvc) Nothing Nothing
              Nothing Nothing Nothing
              Nothing Nothing 

------------------------------------------------------------------------------
-- | Update a `Card`, any fields not specified will remain the same
updateCardBase
    :: FromJSON a
    => URL                  -- ^ The `Recipient` or `Customer` on which to make the `Card` value can be "recipients" or "customers" only
    -> ID                   -- ^ The `RecipientId` or `CustomerId` for which the `Card` can be made
    -> Either CardId RecipientCardId  -- ^ The `CardId` associated with the `Card` to be updated
    -> Maybe Name           -- ^ Name of `Recipient` or `Customer` to be used for `Card`
    -> Maybe AddressCity    -- ^ City associated with `Card`
    -> Maybe AddressCountry -- ^ Country associated with `Card`
    -> Maybe AddressLine1   -- ^ Address Line 1 associated with `Card`
    -> Maybe AddressLine2   -- ^ Address Line 2 associated with `Card`
    -> Maybe AddressState   -- ^ Address State 2 associated with `Card`
    -> Maybe AddressZip     -- ^ Address Zip associated with `Card`
    -> Stripe a
updateCardBase
    requestType
    requestId
    cardid
    name
    addressCity
    addressCountry
    addressLine1
    addressLine2
    addressState
    addressZip  = callAPI request
  where request = StripeRequest POST url params
        url     = requestType </> requestId </> "cards" </> case cardid of
                                                              Right x -> getRecipientCardId x
                                                              Left  x -> getCardId x
        params  = getParams [
                     ("name", name)
                   , ("address_city", (\(AddressCity x) -> x) <$> addressCity)
                   , ("address_country", (\(AddressCountry x) -> x) <$> addressCountry)
                   , ("address_line1", (\(AddressLine1 x) -> x) <$> addressLine1 )
                   , ("address_line2", (\(AddressLine2 x) -> x) <$> addressLine2 )
                   , ("address_state", (\(AddressState x) -> x) <$> addressState )
                   , ("address_zip", (\(AddressZip x) -> x) <$> addressZip )
                   ]

------------------------------------------------------------------------------
-- | Update a `Customer` `Card`
updateCustomerCard
    :: CustomerId           -- ^ `CustomerId` associated with the `Card` to be updated
    -> CardId               -- ^ The `CardId` associated with the `Card` to be updated
    -> Maybe Name           -- ^ Name of `Recipient` or `Customer` to be used for `Card`
    -> Maybe AddressCity    -- ^ City associated with `Card`
    -> Maybe AddressCountry -- ^ Country associated with `Card`
    -> Maybe AddressLine1   -- ^ Address Line 1 associated with `Card`
    -> Maybe AddressLine2   -- ^ Address Line 2 associated with `Card`
    -> Maybe AddressState   -- ^ Address State 2 associated with `Card`
    -> Maybe AddressZip     -- ^ Address Zip associated with `Card`
    -> Stripe Card
updateCustomerCard
    customerid
    cardid = updateCardBase "customers" (getCustomerId customerid) (Left cardid)

------------------------------------------------------------------------------
-- | Update a `Recipient` `Card`
updateRecipientCard
    :: RecipientId          -- ^ `RecipientId` associated with the `Card` to be updated
    -> RecipientCardId      -- ^ The `CardId` associated with the `Card` to be updated
    -> Maybe Name           -- ^ Name of `Recipient` or `Customer` to be used for `Card`
    -> Maybe AddressCity    -- ^ City associated with `Card`
    -> Maybe AddressCountry -- ^ Country associated with `Card`
    -> Maybe AddressLine1   -- ^ Address Line 1 associated with `Card`
    -> Maybe AddressLine2   -- ^ Address Line 2 associated with `Card`
    -> Maybe AddressState   -- ^ Address State 2 associated with `Card`
    -> Maybe AddressZip     -- ^ Address Zip associated with `Card`
    -> Stripe RecipientCard
updateRecipientCard
    recipientid
    cardid = updateCardBase "recipients" (getRecipientId recipientid) (Right cardid)

------------------------------------------------------------------------------
-- | Base Request for retrieving cards from either a `Customer` or `Recipient`
getCardBase
    :: FromJSON a
    => URL          -- ^ The type of the request to support (recipient or customer)
    -> ID           -- ^ `CustomerId` or `RecipientId` of the `Card` to retrieve
    -> ID           -- ^ `CardId` or `RecipientCardId` of the `Card` or `RecipientCard` to retrieve
    -> ExpandParams -- ^ `ExpandParams` of the `Card`
    -> Stripe a
getCardBase
    requestType
    requestId
    cardid
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = requestType </> requestId </> "cards" </> cardid
        params  = toExpandable expandParams

------------------------------------------------------------------------------
-- | Get card by `CustomerId` and `CardId`
getCustomerCard
    :: CustomerId -- ^ `CustomerId` of the `Card` to retrieve
    -> CardId     -- ^ `CardId` of the card to retrieve
    -> Stripe Card
getCustomerCard
    customerid cardid = getCustomerCardExpandable customerid cardid []

------------------------------------------------------------------------------
-- | Get card by `CustomerId` and `CardId` with `ExpandParams`
getCustomerCardExpandable
    :: CustomerId   -- ^ `CustomerId` of the `Card` to retrieve
    -> CardId       -- ^ `CardId` of the card to retrieve
    -> ExpandParams -- ^ `ExpandParams` of the card to retrieve
    -> Stripe Card
getCustomerCardExpandable
    customerid cardid expandParams =
      getCardBase "customers" (getCustomerId customerid) (getCardId cardid) expandParams

------------------------------------------------------------------------------
-- | Get card by `RecipientId` and `CardId`
getRecipientCard
    :: RecipientId     -- ^ `RecipientId` of the `Card` to retrieve
    -> RecipientCardId -- ^ `CardId` of the `Recipient` `Card` to retrieve
    -> Stripe RecipientCard
getRecipientCard
  recipientid
  cardid = getRecipientCardExpandable recipientid cardid []

------------------------------------------------------------------------------
-- | Get card by `RecipientId` and `CardId`
getRecipientCardExpandable
    :: RecipientId     -- ^ `RecipientId` of the `Card` to retrieve
    -> RecipientCardId -- ^ `CardId` of the `Recipient` `Card` to retrieve
    -> ExpandParams    -- ^ `ExpandParams` of the `Recipient` `Card` to retrieve
    -> Stripe RecipientCard
getRecipientCardExpandable
    recipientid
    cardid
    expandParams
      = getCardBase "recipients" (getRecipientId recipientid)
          (getRecipientCardId cardid) expandParams

------------------------------------------------------------------------------
-- | Base Request for retrieving `Customer` cards
getCustomerCardsBase
    :: CustomerId           -- ^ `CustomerId` of the `Card` to retrieve
    -> Maybe Limit          -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter CardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore CardId  -- ^ Paginate ending before the following `CardId`
    -> ExpandParams         -- ^ Expansion on `Card`
    -> Stripe (StripeList Card)
getCustomerCardsBase
    customerid
    limit
    startingAfter
    endingBefore
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = "customers" </> getCustomerId customerid </> "cards"
        params  = getParams [
            ("limit", toText `fmap` limit )
          , ("starting_after", (\(CardId x) -> x) `fmap` startingAfter)
          , ("ending_before", (\(CardId x) -> x) `fmap` endingBefore)
          ] ++ toExpandable expandParams 

------------------------------------------------------------------------------
-- | Base Request for retrieving `Customer` or `Recipient` cards
getRecipientCardsBase
    :: RecipientId                   -- ^ `RecipientId` of the `Card` to retrieve
    -> Maybe Limit                   -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter RecipientCardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore RecipientCardId  -- ^ Paginate ending before the following `CardId`
    -> ExpandParams                  -- ^ Expansion on `Card`
    -> Stripe (StripeList RecipientCard)
getRecipientCardsBase
    recipientid
    limit
    startingAfter
    endingBefore
    expandParams = callAPI request
  where request = StripeRequest GET url params
        url     = "recipients" </> getRecipientId recipientid </> "cards"
        params  = getParams [
            ("limit", toText `fmap` limit )
          , ("starting_after", (\(RecipientCardId x) -> x) `fmap` startingAfter)
          , ("ending_before", (\(RecipientCardId x) -> x) `fmap` endingBefore)
          ] ++ toExpandable expandParams 


------------------------------------------------------------------------------
-- | Retrieve all cards associated with a `Customer`
getCustomerCards
    :: CustomerId           -- ^ The `CustomerId` associated with the cards
    -> Maybe Limit          -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter CardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore CardId  -- ^ Paginate ending before the following `CardId`
    -> Stripe (StripeList Card)
getCustomerCards
    customerid
    limit
    startingAfter
    endingBefore
    = getCustomerCardsExpandable customerid limit startingAfter endingBefore []

------------------------------------------------------------------------------
-- | Retrieve all cards associated with a `Customer`
getCustomerCardsExpandable
    :: CustomerId           -- ^ The `CustomerId` associated with the cards
    -> Maybe Limit          -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter CardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore CardId  -- ^ Paginate ending before the following `CardId`
    -> ExpandParams         -- ^ Expansion on `Card`
    -> Stripe (StripeList Card)
getCustomerCardsExpandable
    customerid
    limit
    startingAfter
    endingBefore
    expandParams =
      getCustomerCardsBase customerid
        limit startingAfter endingBefore expandParams

------------------------------------------------------------------------------
-- | Retrieve all cards associated with a `Recipient`
getRecipientCards
    :: RecipientId                   -- ^ The `RecipientId` associated with the cards
    -> Maybe Limit                   -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter RecipientCardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore RecipientCardId  -- ^ Paginate ending before the following `CardId`
    -> Stripe (StripeList RecipientCard)
getRecipientCards
    recipientid
    limit
    startingAfter
    endingBefore =
      getRecipientCardsExpandable recipientid limit
        startingAfter endingBefore []

------------------------------------------------------------------------------
-- | Retrieve all cards associated with a `Recipient`
getRecipientCardsExpandable
    :: RecipientId                   -- ^ The `RecipientId` associated with the cards
    -> Maybe Limit                   -- ^ Defaults to 10 if `Nothing` specified
    -> StartingAfter RecipientCardId -- ^ Paginate starting after the following `CardId`
    -> EndingBefore RecipientCardId  -- ^ Paginate ending before the following `CardId`
    -> ExpandParams                  -- ^ The `ExpandParams` of the object to be expanded
    -> Stripe (StripeList RecipientCard)
getRecipientCardsExpandable
    recipientid
    limit
    startingAfter
    endingBefore
    expandParams =
      getRecipientCardsBase recipientid
        limit startingAfter endingBefore expandParams

------------------------------------------------------------------------------
-- | Removes a card from a `Customer`
deleteCustomerCard
    :: CustomerId -- ^ `CustomerId` of the `Card` to retrieve
    -> CardId     -- ^ `CardId` associated with `Card` to be deleted
    -> Stripe StripeDeleteResult
deleteCustomerCard
    customerid
    cardid = callAPI request
  where request = StripeRequest DELETE url params
        url     = "customers" </> getCustomerId customerid </> "cards" </> getCardId cardid
        params  = []

------------------------------------------------------------------------------
-- | Removes a card from a `Customer`
deleteRecipientCard
    :: RecipientId     -- ^ The `RecipientId` associated with the `Card` to be removed
    -> RecipientCardId -- ^ The `CardId` of the `Card` to be removed
    -> Stripe StripeDeleteResult
deleteRecipientCard
    recipientid 
    cardid      = callAPI request
  where request = StripeRequest DELETE url params
        url     = "recipients" </> getRecipientId recipientid
                               </> "cards"
                               </> getRecipientCardId cardid
        params  = []