from python_graphql_client import GraphqlClient
from requests.auth import HTTPBasicAuth
from requests.exceptions import HTTPError, ConnectionError
import json
import jsonpickle


from .config import BuyCoinConfig
from BuyCoin.objects.errors import QueryError, ParameterNotAllowed


class Manager:
    """
    Abstract Base Class
    """
    BUYCOIN_URL = None
    USERNAME = None
    PASSWORD = None

    def __init__(self):
        super().__init__()
        if type(self) is Manager:
            raise TypeError("Can not make instance of abstract base class")

        if not BuyCoinConfig.BUYCOIN_SECRET_KEY or not BuyCoinConfig.BUYCOIN_PUBLIC_KEY:
            raise ValueError("No secret key or public key found, \
                             assign values using BuyCoinConfig.BUYCOIN_PUBLIC_KEY = SECRET_KEY and \
                             BuyCoinConfig.BUYCOIN_PUBLIC_KEY = PUBLIC_KEY")

        self.USERNAME = BuyCoinConfig.BUYCOIN_PUBLIC_KEY
        self.PASSWORD = BuyCoinConfig.BUYCOIN_SECRET_KEY
        self.BUYCOIN_URL = BuyCoinConfig.BUYCOIN_URL

    def _create_request_args(self):
        """
        Returns required headers
        """
        auth = HTTPBasicAuth(self.USERNAME, self.PASSWORD)

        return auth

    def to_json(self, data):
        '''
        Method to serialize class instance
        '''
        data = json.JSONDecoder().decode(jsonpickle.encode(data))
        data.pop("py/object")
        return data

    def _initialize_client(self):
        """
        Create GraphQL client for executing queries
        Returns client
        """
        auth = self._create_request_args()

        try:
            _client = GraphqlClient(endpoint=self.BUYCOIN_URL, auth=auth)
        except (HTTPError, ConnectionError) as e:
            return e
        else:
            return _client

    def _perform_request(self, query, variables={}):
        self._client = self._initialize_client()
        if not query:
            return QueryError('Invalid Query')
        try:
            request = self._client.execute(query=query, variables=variables)
        except (ConnectionError, HTTPError) as e:
            raise e
        except QueryError as e:
            return e.response
        else:
            return request


class CustomerWalletManager(Manager):
    """
    Manager handles every transaction concerning cryptocurrency
    Includes buying, selling, getting prices, creating addresses and getting crypto-balance

    """
    _BUY_QUERY = """
        mutation BuyCoin($price: ID!, $coin_amount: BigDecimal!, $cryptocurrency: Cryptocurrency){
                buy(price: $price, coin_amount: $coin_amount, cryptocurrency: $cryptocurrency) {
                    id
                    cryptocurrency
                    status
                    totalCoinAmount
                    side
                }
            }
        """
    _SELL_QUERY = """
        mutation SellCoin($price: ID!, $coin_amount: BigDecimal!, $currency: Cryptocurrency){
            sell(price: $price, coin_amount: $coin_amount, cryptocurrency: $currency) {
                id
                cryptocurrency
                status
                totalCoinAmount
                side
            }
        }

    """

    _GET_PRICE_QUERY = """
        query {
            getPrices {
                id
                cryptocurrency
                buyPricePerCoin
                minBuy
                maxBuy
                expiresAt
            }
        }
        """
    _GET_CRYPTO_PRICE_QUERY = """
        query GetBuyCoinsPrices($cryptocurrency: Cryptocurrency, $side:OrderSide) {
            getPrices(cryptocurrency: $cryptocurrency, side:$side){
                buyPricePerCoin
                cryptocurrency
                id
                maxBuy
                maxSell
                minBuy
                minCoinAmount
                minSell
                sellPricePerCoin
                status
            }
        }
    """

    _CREATE_ADDRESS_QUERY = """
        mutation CreateWalletAddress($cryptocurrency: Cryptocurrency) {
            createAddress(cryptocurrency: $cryptocurrency) {
                cryptocurrency
                address
            }
        }
    """

    _SEND_CRYPTO_QUERY = """
        mutation SendCrypto($cryptocurrency: Cryptocurrency, $address: String!, $amount: BigDecimal!){
            send(cryptocurrency: $cryptocurrency, amount: $amount, address: $address) {
                id
                address
                amount
                cryptocurrency
                fee
                status
                transaction {
                    txhash
                    id
                }
            }
        }
    """
    _BALANCE_QUERY = """
        query {
            getBalances {
                id
                cryptocurrency
                confirmedBalance
            }
        }
    """

    def __init__(self):
        super().__init__()

    def get_prices(self, cryptocurrency=None, side=None):
        """
        Retrieve all cryptocurrencies current price

        Args:
            cryptocurrency: Retrieve specific cryptocurrency price

        Returns:
            response: a list of cryptocurrency and their prices
        """
        if cryptocurrency:
            if not side:
                raise ParameterNotAllowed("Please specify side, either a 'buy' or 'sell' side", status=400)

            query = self._GET_CRYPTO_PRICE_QUERY
            variables = {
                "cryptocurrency": cryptocurrency,
                "side": side
            }
        else:
            query = self._GET_PRICE_QUERY
            variables = {}

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]["getPrices"]

    def initialize_transaction(self, wallet_transaction):
        data = self.to_json(wallet_transaction)
        operation = data.pop("operation")
        variables = {**data}

        if operation == "buy":
            query = self._BUY_QUERY
            price = self.get_prices(cryptocurrency=data["cryptocurrency"], side=operation)
            variables = {**variables, "price": price[0]["id"]}
        elif operation == "sell":
            query = self._SELL_QUERY
            price = self.get_prices(cryptocurrency=data["cryptocurrency"], side=operation)
            variables = {**variables, "price": price[0]["id"]}
        elif operation == "create":
            query = self._CREATE_ADDRESS_QUERY
        elif operation == "balance":
            query = self._BALANCE_QUERY
            variables = {}
        elif operation == "send":
            query = self._SEND_CRYPTO_QUERY

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]


class P2PManager(Manager):
    """
    Handles every peer to peer transactions

    Place limit orders, get market cap history, get placed orders
    """

    _MARKET_ORDER_QUERY = """
        query {
          getMarketBook  {
            dynamicPriceExpiry
            orders {
              edges {
                node {
                  id
                  cryptocurrency
                  coinAmount
                  side
                  status
                  createdAt
                  pricePerCoin
                  priceType
                  staticPrice
                  dynamicExchangeRate
                }
              }
            }
          }
        }
    """

    _DYNAMIC_PRICE_EXPIRY_QUERY = """
        query {
            getOrders(status: open) {
                dynamicPriceExpiry
            }
        }
    """

    _PLACE_LIMIT_ORDER = """
        mutation PostLimitOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency,
        $staticPrice: BigDecimal, $dynamicExchangeRate:BigDecimal, $priceType: PriceType!){
            postLimitOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency,
            staticPrice: $staticPrice, priceType: $priceType){
                id
                cryptocurrency
                coinAmount
                side
                status
                createdAt
                pricePerCoin
                priceType
                staticPrice
                dynamicExchangeRate
            }
        }
    """

    _POST_MARKET_ORDER = """
        mutation PostMarketOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency!){
            postMarketOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency){
                id
                cryptocurrency
                coinAmount
                side
                status
                createdAt
                pricePerCoin
                priceType
                staticPrice
                dynamicExchangeRate
            }
        }
            """

    def initialize_transaction(self, p2p_transaction):
        data = self.to_json(p2p_transaction)
        operation = data.pop("operation")
        variables = {**data}

        if operation == "plo":
            try:
                if not variables["dynamicExchangeRate"]:
                    variables.pop("dynamicExchangeRate")
                elif not variables["staticPrice"]:
                    variables.pop("staticPrice")
            except:
                pass
            self.post_limit_order(variables)
        elif operation == "pmo":
            self.post_market_order(variables)

    def post_limit_order(self, variables):
        """
        Places limit order for the supplied cryptocurrency.
        Args:
            order_side (str): The side of the order. This could be `buy` or `sell`.
            coin_amount (str): The amount the limit order is based on.
            currency (str): The cryptocurrency involved in the limit order.
            static_price (str, optional): Static price for the cryptocurrency in Naira.
            price_type (str): Static or dynamic price for the cryptocurrency.
        Returns:
            response: A JSON object containing the result from the request.
        """
        query = self._PLACE_LIMIT_ORDER

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def post_market_order(self, variables):
        """
        Places market order for the supplied cryptocurrency.
        Args:
            order_side (str): The side of the order. This could be `buy` or `sell`.
            coin_amount (str): The amount the limit order is based on.
            currency (str): The cryptocurrency involved in the limit order.
        Returns:
            response: A JSON object containing the result from the request.
        """
        query = self._POST_MARKET_ORDER
        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def get_placed_orders(self):
        pass

    def get_market_order(self):
        """
        Retrieves market order history.
        Returns:
            response: A JSON object containing response from the request.
        """
        query = self._MARKET_ORDER_QUERY
        variables = {}
        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def get_dynamic_price_expiry(self):
        """
        Retrieves when next dynamic prices will be updated
        Returns:
            response: A JSON object containing response from the request.
        """
        query = self._DYNAMIC_PRICE_EXPIRY_QUERY
        variables = {}

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]


class NGNTManager(Manager):
    pass

from python_graphql_client import GraphqlClient
from requests.auth import HTTPBasicAuth
from requests.exceptions import HTTPError, ConnectionError
import json
import jsonpickle


from .config import BuyCoinConfig
from BuyCoin.objects.errors import QueryError, ParameterNotAllowed


class Manager:
    """
    Abstract Base Class
    """
    BUYCOIN_URL = None
    USERNAME = None
    PASSWORD = None

    def __init__(self):
        super().__init__()
        if type(self) is Manager:
            raise TypeError("Can not make instance of abstract base class")

        if not BuyCoinConfig.BUYCOIN_SECRET_KEY or not BuyCoinConfig.BUYCOIN_PUBLIC_KEY:
            raise ValueError("No secret key or public key found, \
                             assign values using BuyCoinConfig.BUYCOIN_PUBLIC_KEY = SECRET_KEY and \
                             BuyCoinConfig.BUYCOIN_PUBLIC_KEY = PUBLIC_KEY")

        self.USERNAME = BuyCoinConfig.BUYCOIN_PUBLIC_KEY
        self.PASSWORD = BuyCoinConfig.BUYCOIN_SECRET_KEY
        self.BUYCOIN_URL = BuyCoinConfig.BUYCOIN_URL

    def _create_request_args(self):
        """
        Returns required headers
        """
        auth = HTTPBasicAuth(self.USERNAME, self.PASSWORD)

        return auth

    def to_json(self, data):
        '''
        Method to serialize class instance
        '''
        data = json.JSONDecoder().decode(jsonpickle.encode(data))
        data.pop("py/object")
        return data

    def _initialize_client(self):
        """
        Create GraphQL client for executing queries
        Returns client
        """
        auth = self._create_request_args()

        try:
            _client = GraphqlClient(endpoint=self.BUYCOIN_URL, auth=auth)
        except (HTTPError, ConnectionError) as e:
            return e
        else:
            return _client

    def _perform_request(self, query, variables={}):
        self._client = self._initialize_client()
        if not query:
            return QueryError('Invalid Query')
        try:
            request = self._client.execute(query=query, variables=variables)
        except (ConnectionError, HTTPError) as e:
            raise e
        except QueryError as e:
            return e.response
        else:
            return request


class CustomerWalletManager(Manager):
    """
    Manager handles every transaction concerning cryptocurrency
    Includes buying, selling, getting prices, creating addresses and getting crypto-balance

    """
    _BUY_QUERY = """
        mutation BuyCoin($price: ID!, $coin_amount: BigDecimal!, $cryptocurrency: Cryptocurrency){
                buy(price: $price, coin_amount: $coin_amount, cryptocurrency: $cryptocurrency) {
                    id
                    cryptocurrency
                    status
                    totalCoinAmount
                    side
                }
            }
        """
    _SELL_QUERY = """
        mutation SellCoin($price: ID!, $coin_amount: BigDecimal!, $currency: Cryptocurrency){
            sell(price: $price, coin_amount: $coin_amount, cryptocurrency: $currency) {
                id
                cryptocurrency
                status
                totalCoinAmount
                side
            }
        }

    """

    _GET_PRICE_QUERY = """
        query {
            getPrices {
                id
                cryptocurrency
                buyPricePerCoin
                minBuy
                maxBuy
                expiresAt
            }
        }
        """
    _GET_CRYPTO_PRICE_QUERY = """
        query GetBuyCoinsPrices($cryptocurrency: Cryptocurrency, $side:OrderSide) {
            getPrices(cryptocurrency: $cryptocurrency, side:$side){
                buyPricePerCoin
                cryptocurrency
                id
                maxBuy
                maxSell
                minBuy
                minCoinAmount
                minSell
                sellPricePerCoin
                status
            }
        }
    """

    _CREATE_ADDRESS_QUERY = """
        mutation CreateWalletAddress($cryptocurrency: Cryptocurrency) {
            createAddress(cryptocurrency: $cryptocurrency) {
                cryptocurrency
                address
            }
        }
    """

    _SEND_CRYPTO_QUERY = """
        mutation SendCrypto($cryptocurrency: Cryptocurrency, $address: String!, $amount: BigDecimal!){
            send(cryptocurrency: $cryptocurrency, amount: $amount, address: $address) {
                id
                address
                amount
                cryptocurrency
                fee
                status
                transaction {
                    txhash
                    id
                }
            }
        }
    """
    _BALANCE_QUERY = """
        query {
            getBalances {
                id
                cryptocurrency
                confirmedBalance
            }
        }
    """

    def __init__(self):
        super().__init__()

    def get_prices(self, cryptocurrency=None, side=None):
        """
        Retrieve all cryptocurrencies current price

        Args:
            cryptocurrency: Retrieve specific cryptocurrency price

        Returns:
            response: a list of cryptocurrency and their prices
        """
        if cryptocurrency:
            if not side:
                raise ParameterNotAllowed("Please specify side, either a 'buy' or 'sell' side", status=400)

            query = self._GET_CRYPTO_PRICE_QUERY
            variables = {
                "cryptocurrency": cryptocurrency,
                "side": side
            }
        else:
            query = self._GET_PRICE_QUERY
            variables = {}

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]["getPrices"]

    def initialize_transaction(self, wallet_transaction):
        data = self.to_json(wallet_transaction)
        operation = data.pop("operation")
        variables = {**data}

        if operation == "buy":
            query = self._BUY_QUERY
            price = self.get_prices(cryptocurrency=data["cryptocurrency"], side=operation)
            variables = {**variables, "price": price[0]["id"]}
        elif operation == "sell":
            query = self._SELL_QUERY
            price = self.get_prices(cryptocurrency=data["cryptocurrency"], side=operation)
            variables = {**variables, "price": price[0]["id"]}
        elif operation == "create":
            query = self._CREATE_ADDRESS_QUERY
        elif operation == "balance":
            query = self._BALANCE_QUERY
            variables = {}
        elif operation == "send":
            query = self._SEND_CRYPTO_QUERY

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]


class P2PManager(Manager):
    """
    Handles every peer to peer transactions

    Place limit orders, get market cap history, get placed orders
    """

    _MARKET_ORDER_QUERY = """
        query {
          getMarketBook  {
            dynamicPriceExpiry
            orders {
              edges {
                node {
                  id
                  cryptocurrency
                  coinAmount
                  side
                  status
                  createdAt
                  pricePerCoin
                  priceType
                  staticPrice
                  dynamicExchangeRate
                }
              }
            }
          }
        }
    """

    _DYNAMIC_PRICE_EXPIRY_QUERY = """
        query {
            getOrders(status: open) {
                dynamicPriceExpiry
            }
        }
    """

    _PLACE_LIMIT_ORDER = """
        mutation PostLimitOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency,
        $staticPrice: BigDecimal, $dynamicExchangeRate:BigDecimal, $priceType: PriceType!){
            postLimitOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency,
            staticPrice: $staticPrice, priceType: $priceType){
                id
                cryptocurrency
                coinAmount
                side
                status
                createdAt
                pricePerCoin
                priceType
                staticPrice
                dynamicExchangeRate
            }
        }
    """

    _POST_MARKET_ORDER = """
        mutation PostMarketOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency!){
            postMarketOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency){
                id
                cryptocurrency
                coinAmount
                side
                status
                createdAt
                pricePerCoin
                priceType
                staticPrice
                dynamicExchangeRate
            }
        }
            """

    def initialize_transaction(self, p2p_transaction):
        data = self.to_json(p2p_transaction)
        operation = data.pop("operation")
        variables = {**data}

        if operation == "plo":
            try:
                if not variables["dynamicExchangeRate"]:
                    variables.pop("dynamicExchangeRate")
                elif not variables["staticPrice"]:
                    variables.pop("staticPrice")
            except:
                pass
            self.post_limit_order(variables)
        elif operation == "pmo":
            self.post_market_order(variables)

    def post_limit_order(self, variables):
        """
        Places limit order for the supplied cryptocurrency.
        Args:
            order_side (str): The side of the order. This could be `buy` or `sell`.
            coin_amount (str): The amount the limit order is based on.
            currency (str): The cryptocurrency involved in the limit order.
            static_price (str, optional): Static price for the cryptocurrency in Naira.
            price_type (str): Static or dynamic price for the cryptocurrency.
        Returns:
            response: A JSON object containing the result from the request.
        """
        query = self._PLACE_LIMIT_ORDER

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def post_market_order(self, variables):
        """
        Places market order for the supplied cryptocurrency.
        Args:
            order_side (str): The side of the order. This could be `buy` or `sell`.
            coin_amount (str): The amount the limit order is based on.
            currency (str): The cryptocurrency involved in the limit order.
        Returns:
            response: A JSON object containing the result from the request.
        """
        query = self._POST_MARKET_ORDER
        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def get_placed_orders(self):
        pass

    def get_market_order(self):
        """
        Retrieves market order history.
        Returns:
            response: A JSON object containing response from the request.
        """
        query = self._MARKET_ORDER_QUERY
        variables = {}
        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]

    def get_dynamic_price_expiry(self):
        """
        Retrieves when next dynamic prices will be updated
        Returns:
            response: A JSON object containing response from the request.
        """
        query = self._DYNAMIC_PRICE_EXPIRY_QUERY
        variables = {}

        try:
            response = self._perform_request(query=query, variables=variables)
        except Exception as e:
            raise e
        else:
            return response["data"]


class NGNTManager(Manager):
    pass

Abstract Base Class Returns required headers Method to serialize class instance Create GraphQL client for executing queries         Returns client Manager handles every transaction concerning cryptocurrency     Includes buying, selling, getting prices, creating addresses and getting crypto-balance mutation BuyCoin($price: ID!, $coin_amount: BigDecimal!, $cryptocurrency: Cryptocurrency){                 buy(price: $price, coin_amount: $coin_amount, cryptocurrency: $cryptocurrency) {                     id                     cryptocurrency                     status                     totalCoinAmount                     side                 }             } mutation SellCoin($price: ID!, $coin_amount: BigDecimal!, $currency: Cryptocurrency){             sell(price: $price, coin_amount: $coin_amount, cryptocurrency: $currency) {                 id                 cryptocurrency                 status                 totalCoinAmount                 side             }         } query {             getPrices {                 id                 cryptocurrency                 buyPricePerCoin                 minBuy                 maxBuy                 expiresAt             }         } query GetBuyCoinsPrices($cryptocurrency: Cryptocurrency, $side:OrderSide) {             getPrices(cryptocurrency: $cryptocurrency, side:$side){                 buyPricePerCoin                 cryptocurrency                 id                 maxBuy                 maxSell                 minBuy                 minCoinAmount                 minSell                 sellPricePerCoin                 status             }         } mutation CreateWalletAddress($cryptocurrency: Cryptocurrency) {             createAddress(cryptocurrency: $cryptocurrency) {                 cryptocurrency                 address             }         } mutation SendCrypto($cryptocurrency: Cryptocurrency, $address: String!, $amount: BigDecimal!){             send(cryptocurrency: $cryptocurrency, amount: $amount, address: $address) {                 id                 address                 amount                 cryptocurrency                 fee                 status                 transaction {                     txhash                     id                 }             }         } query {             getBalances {                 id                 cryptocurrency                 confirmedBalance             }         } Retrieve all cryptocurrencies current price          Args:             cryptocurrency: Retrieve specific cryptocurrency price          Returns:             response: a list of cryptocurrency and their prices Handles every peer to peer transactions      Place limit orders, get market cap history, get placed orders query {           getMarketBook  {             dynamicPriceExpiry             orders {               edges {                 node {                   id                   cryptocurrency                   coinAmount                   side                   status                   createdAt                   pricePerCoin                   priceType                   staticPrice                   dynamicExchangeRate                 }               }             }           }         } query {             getOrders(status: open) {                 dynamicPriceExpiry             }         } mutation PostLimitOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency,         $staticPrice: BigDecimal, $dynamicExchangeRate:BigDecimal, $priceType: PriceType!){             postLimitOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency,             staticPrice: $staticPrice, priceType: $priceType){                 id                 cryptocurrency                 coinAmount                 side                 status                 createdAt                 pricePerCoin                 priceType                 staticPrice                 dynamicExchangeRate             }         } mutation PostMarketOrder($orderSide: OrderSide!, $coinAmount: BigDecimal!, $cryptocurrency: Cryptocurrency!){             postMarketOrder(orderSide: $orderSide, coinAmount: $coinAmount, cryptocurrency: $cryptocurrency){                 id                 cryptocurrency                 coinAmount                 side                 status                 createdAt                 pricePerCoin                 priceType                 staticPrice                 dynamicExchangeRate             }         } Places limit order for the supplied cryptocurrency.         Args:             order_side (str): The side of the order. This could be `buy` or `sell`.             coin_amount (str): The amount the limit order is based on.             currency (str): The cryptocurrency involved in the limit order.             static_price (str, optional): Static price for the cryptocurrency in Naira.             price_type (str): Static or dynamic price for the cryptocurrency.         Returns:             response: A JSON object containing the result from the request. Places market order for the supplied cryptocurrency.         Args:             order_side (str): The side of the order. This could be `buy` or `sell`.             coin_amount (str): The amount the limit order is based on.             currency (str): The cryptocurrency involved in the limit order.         Returns:             response: A JSON object containing the result from the request. Retrieves market order history.         Returns:             response: A JSON object containing response from the request. Retrieves when next dynamic prices will be updated         Returns:             response: A JSON object containing response from the request.