import requests, json, time, hashlib, hmac


class CoinifyAPI:
    api_key = None
    """Coinify API key. Get yours at https://www.coinify.com/merchant/api"""

    api_secret = None
    """Coinify API secret. Get yours at https://www.coinify.com/merchant/api"""

    api_base_url = None
    """Base URL to the Coinify API"""

    API_DEFAULT_BASE_URL = "https://api.coinify.com"

    PLUGIN_NAME="Coinify python sdk"
    PLUGIN_VERSION="1.0"

    def __init__(self, api_key=None, api_secret=None, api_base_url=None):
        """
        Create an instance of the CoinifyAPI class.
        Provide your API key and API secret.

        Set api_base_url to None to use default
        """
        self.api_key = api_key
        self.api_secret = api_secret
        self.api_base_url = api_base_url or self.API_DEFAULT_BASE_URL

    def invoices_list(self, limit=None, offset=None, include_expired=None):
        """
        Returns an array of your Coinify invoices

        See https://www.coinify.com/docs/api/#list-all-invoices
        """
        query_params = {}

        if limit is not None:
            query_params['limit'] = limit
        if offset is not None:
            query_params['offset'] = offset
        if include_expired is not None:
            query_params['include_expired'] = include_expired

        return self.call_api_authenticated('/v3/invoices', query_params=query_params)

    def invoice_create(self, amount, currency, description=None, custom=None, callback_url=None, callback_email=None,
                       return_url=None, cancel_url=None, input_currency=None, input_return_address=None):

        """
        Create a new invoice.

        See https://www.coinify.com/docs/api/#create-an-invoice
        """
        params = {
            'amount': amount,
            'currency': currency,
            'plugin_name': self.PLUGIN_NAME,
            'plugin_version': self.PLUGIN_VERSION
        }

        if description is not None:
            params['description'] = description
        if custom is not None:
            params['custom'] = custom
        if callback_url is not None:
            params['callback_url'] = callback_url
        if callback_email is not None:
            params['callback_email'] = callback_email
        if return_url is not None:
            params['return_url'] = return_url
        if cancel_url is not None:
            params['cancel_url'] = cancel_url
        if input_currency is not None:
            params['input_currency'] = input_currency
        if input_return_address is not None:
            params['input_return_address'] = input_return_address

        return self.call_api_authenticated('/v3/invoices', 'POST', params)

    def invoice_get(self, invoice_id):
        """
        Get a specific invoice

        See https://www.coinify.com/docs/api/#get-a-specific-invoice
        """
        path = '/v3/invoices/%d' % (invoice_id,)

        return self.call_api_authenticated(path)

    def invoice_update(self, invoice_id, description=None, custom=None):
        """
        Update the description and custom data of an invoice

        See https://www.coinify.com/docs/api/#update-an-invoice
        """
        params = {}

        if description is not None:
            params['description'] = description
        if custom is not None:
            params['custom'] = custom

        path = '/v3/invoices/%d' % (invoice_id,)

        return self.call_api_authenticated(path, 'PUT', params)

    def invoice_input_create(self, invoice_id, currency, return_address):
        """
        Request for an invoice to be paid with another input currency.

        See https://www.coinify.com/docs/api/#pay-with-another-input-currency
        """
        params = {
            'currency': currency,
            'return_address': return_address
        }

        path = '/v3/invoices/%d/inputs' % (invoice_id,)

        return self.call_api_authenticated(path, 'POST', params)

    def invoice_refunds_list(self, invoice_id):
        """
        List all refunds for an invoice.

        See https://www.coinify.com/docs/api/#list-refunds-for-invoice
        """
        path = '/v3/invoices/%d/refunds' % (invoice_id,)

        return self.call_api_authenticated(path)


    def invoice_refund_create(self, invoice_id, amount, currency,
                              email_address=None, btc_address=None,
                              use_payment_protocol_refund_address=True):
        """
        Request a refund for an invoice

        See https://www.coinify.com/docs/api/#create-refund
        """
        params = {
            'amount': amount,
            'currency': currency
        }

        if email_address is not None:
            params['email_address'] = email_address
        if btc_address is not None:
            params['btc_address'] = btc_address
        if use_payment_protocol_refund_address is not None:
            params['use_payment_protocol_refund_address'] = use_payment_protocol_refund_address

        path = '/v3/invoices/%d/refunds' % (invoice_id,)

        return self.call_api_authenticated(path, 'POST', params)


    def buy_orders_list(self, limit=None, offset=None, include_cancelled=None):
        """
        Returns an array of your Coinify buy orders

        See https://www.coinify.com/docs/api/#list-all-buy-orders
        """

        query_params = {}

        if limit is not None:
            query_params['limit'] = limit
        if offset is not None:
            query_params['offset'] = offset
        if include_cancelled is not None:
            query_params['include_cancelled'] = include_cancelled

        return self.call_api_authenticated('/v3/buys', query_params=query_params)

    def buy_order_create(self, amount, currency, btc_address, instant_order=None,
                         callback_url=None, callback_email=None):
        """
        Create a new buy order

        See https://www.coinify.com/docs/api/#create-a-buy-order
        """
        params = {
            'amount': amount,
            'currency': currency,
            'btc_address': btc_address
        }

        if instant_order is not None:
            params['instant_order'] = instant_order
        if callback_url is not None:
            params['callback_url'] = callback_url
        if callback_email is not None:
            params['callback_email'] = callback_email

        return self.call_api_authenticated('/v3/buys', 'POST', params)

    def buy_order_confirm(self, buy_order_id):
        """
        Confirm a buy order

        See https://www.coinify.com/docs/api/#buy-order-confirm
        """
        path = '/v3/buys/%d/actions/confirm' % (buy_order_id,)

        return self.call_api_authenticated(path, 'PUT')

    def buy_order_get(self, buy_order_id):
        """
        Get a specific buy order

        See https://www.coinify.com/docs/api/#get-a-specific-buy-order
        """
        path = '/v3/buys/%d' % (buy_order_id,)

        return self.call_api_authenticated(path)

    def rates_get(self, currency=None):
        """
        Return buy and sell rates for all available currencies or for the specified currency.
        :param self:
        :param currency|None: A 3-char currency code
        :return:
        """
        if currency is None:
            path = '/v3/rates'
        else:
            path = '/v3/rates/%s' % (currency,)

        return self.call_api_authenticated(path)

    def altrates_get(self, altcoin=None):
        """
        Return rates for all supported altcoins or for the specified altcoin.
        :param self:
        :param altcoin|None: A 3-char altcoin code
        :return:
        """
        if altcoin is None:
            path = '/v3/altrates'
        else:
            path = '/v3/altrates/%s' % (altcoin,)

        return self.call_api_authenticated(path)

    def balance_get(self):
        """
        Get the balance of a merchant
        :param path:
        :return: An array as described in https://www.coinify.com/docs/api/#check-account-balance . If success,
                then the 'data' value contains the balance in BTC and fiat currency and also the base currency
                of the merchant that requests it.
        """
        path = '/v3/balance'
        return self.call_api_authenticated(path)

    def input_currencies_list(self):
        """
        Receive a list of supported input currencies
        """

        return self.call_api('/v3/input-currencies')

    def call_api_authenticated(self, path, method='GET', params={}, query_params={}):
        """
        Perform an authenticated API call, using the
        API key and secret provided in the constructor.

        path: API path, WITH a leading slash, e.g. '/v3/invoices'
        params: dict with parameters to send with the API call

        Returns a dict as described in https://www.coinify.com/docs/api/#response-format,
        or None if the HTTP call couldn't be performed correctly.
        """
        extra_headers = {
            'Authorization': self.generate_authorization_header()
        }

        return self.call_api(path, method, params, query_params, extra_headers)

    def call_api(self, path, method='GET', params={}, query_params={}, headers={}):
        """
        Perform an API call

        path: API path, WITH a leading slash, e.g. '/v3/invoices'
        params: dict with parameters to send with the API call

        Returns a dict as described in https://www.coinify.com/docs/api/#response-format,
        or None if the HTTP call couldn't be performed correctly.
        """
        url = self.api_base_url + path

        headers['Content-Type'] = 'application/json'

        if self.api_key is not None:
            headers['Authorization'] = self.generate_authorization_header()

        r = requests.request(method, url, json=params, headers=headers, params=query_params)
        return r.json()

    def generate_authorization_header(self):
        """
        Generate a nonce and a signature for an API call
        and wrap those in a HTTP header
        """
        # Generate nonce, based on the current Unix timestamp
        nonce = str(int(time.time() * 1000000))

        # Concatenate nonce and API key
        message = nonce + self.api_key

        # Compute signature
        signature = hmac.new(self.api_secret, msg=message, digestmod=hashlib.sha256).hexdigest()

        # Construct the header value
        header_value = 'Coinify apikey="%s", nonce="%s", signature="%s"' % (self.api_key, nonce, signature)

        return header_value
