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

module CoinbasePro.Authenticated.CoinbaseAccounts
    ( CoinbaseAccount (..)
    ) where

import           Data.Aeson        (FromJSON (..), Value (..), withObject, (.:),
                                    (.:?))
import           Data.Aeson.Casing (snakeCase)
import           Data.Aeson.TH     (defaultOptions, deriveJSON,
                                    fieldLabelModifier)
import           Data.Text         (Text)


data BankCountry = BankCountry
    { code :: Text
    , name :: Text
    } deriving Show


deriveJSON defaultOptions
    { fieldLabelModifier = snakeCase
    } ''BankCountry


data WireDepositInformation = WireDepositInformation
    { accountNumber  :: Maybe Text
    , routingNumber  :: Text
    , bankName       :: Text
    , bankAddress    :: Text
    , bankCountry    :: BankCountry
    , accountName    :: Text
    , accountAddress :: Text
    , reference      :: Text
    } deriving Show


instance FromJSON WireDepositInformation where
  parseJSON = withObject "wire deposit information" $ \o -> WireDepositInformation
    <$> o .: "account_number"
    <*> o .: "routing_number"
    <*> o .: "bank_name"
    <*> o .: "bank_address"
    <*> (parseJSON =<< (o .: "bank_country"))
    <*> o .: "account_name"
    <*> o .: "account_address"
    <*> o .: "reference"


data SepaDepositInformation = SepaDepositInformation
    { sIban            :: Text
    , sSwift           :: Text
    , sBankName        :: Text
    , sBankAddress     :: Text
    , sBankCountryName :: Text
    , sAccountName     :: Text
    , sAccountAddress  :: Text
    , sReference       :: Text
    } deriving Show


deriveJSON defaultOptions
    { fieldLabelModifier = snakeCase . drop 1
    } ''SepaDepositInformation


data DepositInformation = Wire WireDepositInformation | Sepa SepaDepositInformation
  deriving Show


-- TODO: this is slightly messy, potentially refactor
instance FromJSON DepositInformation where
    parseJSON = withObject "deposit_information" $ \o -> do
      w <- o .:? "wire_deposit_information"
      case w of
        Just (Object w') -> Wire <$> parseJSON (Object w')
        Nothing -> do
          s <- o .:? "sepa_deposit_information"
          case s of
            Just (Object s') -> Sepa <$> parseJSON (Object s')
            _                -> fail "Unable to parse deposit information"
        _ -> fail "Unable to parse deposit information"


data Account = Account
    { aId       :: Text
    , aName     :: Text
    , aBalance  :: Double
    , aCurrency :: Text
    , aType     :: Text
    , aPrimary  :: Bool
    , aActive   :: Bool
    } deriving (Show)


instance FromJSON Account where
  parseJSON = withObject "account" $ \o -> Account
    <$> o .: "id"
    <*> o .: "name"
    <*> (read <$> o .: "balance")
    <*> o .: "currency"
    <*> o .: "type"
    <*> o .: "primary"
    <*> o .: "active"


data FiatAccount = FiatAccount
    { fAccount            :: Account
    , fDepositInformation :: DepositInformation
    } deriving Show


instance FromJSON FiatAccount where
    parseJSON = withObject "fiat account" $ \o -> FiatAccount
      <$> parseJSON (Object o)
      <*> parseJSON (Object o)


newtype CryptoAccount = CryptoAccount Account
    deriving Show


instance FromJSON CryptoAccount where
  parseJSON = withObject "crypto account" $ \o -> CryptoAccount
    <$> parseJSON (Object o)


data CoinbaseAccount = Fiat FiatAccount | Crypto CryptoAccount
    deriving Show


instance FromJSON CoinbaseAccount where
  parseJSON = withObject "coinbase account" $ \o -> do
      t <- String <$> o .: "type"
      case t of
        "fiat"   -> Fiat <$> parseJSON (Object o)
        "wallet" -> Crypto <$> parseJSON (Object o)
        _        -> fail "Unable to parse coinbase account"
