import logging
import logging as logger
from functools import wraps

import krakenex
import pandas
from pandas import DataFrame
from pykrakenapi import KrakenAPI

from krakee import OrderBuilder
from krakee.api import validators


# @cache decorator to cache responses
def cached(*args, **kwargs):

    func = None
    if len(args) == 1 :
        func = args[0]
    if func:
        always = False
    if not func:
        always = kwargs.get('always')

    def callable (func):

        @wraps (func)
        def wrapper(self, *args, **kwargs):
            func_name = func.__name__
            cachedValue = None
            cachedId = "_".join([func_name])
            if self.is_cached or always:
                if cachedId in self.cache:
                    cachedValue = self.cache[cachedId]
                    logger.info ("Cache::retrieving {}() from cache...".format(func_name))
            if cachedValue is None:
                cachedValue = func(self, *args, **kwargs)
                if self.cache or always:
                    self.cache[cachedId] = cachedValue
            return cachedValue
        return wrapper
    return callable(func) if func else callable


class Krakee:

    def __init__(self, authfile=None, full_caching=False):
        self.cache = {}
        self.is_cached = full_caching
        if authfile:
            with open(authfile, 'r') as f:
                public_key = f.readline().strip()
                secret_key = f.readline().strip()
                self.kapi = KrakenAPI(krakenex.API(public_key, secret_key), crl_sleep=2)
                logger.info("Auth keys successfully loaded from {}".format(authfile))
        else:
            self.kapi = KrakenAPI(krakenex.API(), crl_sleep=2)
            logger.info("Auth token not provided, only public API available")
        self.asset_pairs()

    @cached(always=True)
    def assets(self) -> DataFrame: return self.kapi.get_asset_info().transpose()

    @cached (always=True)
    def asset_pairs(self) -> DataFrame: return self.kapi.get_tradable_asset_pairs().transpose()

    @cached
    def tickers(self, pairs) -> DataFrame:
        validators.assert_list(pairs, "pairs")
        validators.asset_pair(self, pairs)
        assetList = [pairs[n:n + 9] for n in range(0, len(pairs), 9)]
        logger.info ("Tickers::requesting {} set of tickers, it will take aprox. {}s".format(len(assetList), (len(assetList)-1)*5))
        tickers = [self.kapi.get_ticker_information(",".join(assets)) for assets in assetList]
        tickerDf = pandas.concat(tickers).transpose()
        logger.info ("Tickers::retrieved {} tickers".format(len(tickerDf.columns)))
        return tickerDf

    """Returns OHLC data for given pairs
    
    Returns the OHLC data for given pairs as a map pair->OHLC data. 
    This may take a while if several asset pairs are provided.
    
    Attributes
    ----------
    pairs: list of asset pairs
    interval: one of allowed interval values
    since: optional since timestamp
    """
    @cached
    def ohlc(self, pairs, interval: str = "1min", since=None) -> map:
        validators.assert_list(pairs, "pairs")
        validators.asset_pair (self, pairs)
        intervals = {"1min": 1, "5min": 5, "15min": 15, "30min": 30, "1h": 60, "4h": 240, "1d": 1440, "7d": 10080, "15d": 21600}
        validators.assert_interval(interval, intervals)
        ohlcList = {pair:self.kapi.get_ohlc_data(pair, intervals[interval], since) for pair in pairs}
        return ohlcList

    @cached
    def order_book(self, pair, count=None) -> (DataFrame, DataFrame):
        validators.asset_pair(self, pair)
        return self.kapi.get_order_book(pair, count)

    @cached
    def trades(self, pair, since=None) -> DataFrame:
        validators.asset_pair(self, pair)
        return self.kapi.get_recent_trades (pair, since)

    @cached
    def spread(self, pair, since=None) -> DataFrame:
        validators.asset_pair(self, pair)
        return self.kapi.get_recent_spread_data (pair, since)

    # Private API

    def add_order (self, order_builder: OrderBuilder):
        return self.kapi.api.query_private("AddTrade", order_builder.build())

    def balance(self): return self.kapi.get_account_balance()

    def trade_balance (self): return self.kapi.get_trade_balance()

    def open_orders (self): return self.kapi.get_open_orders()
    def closed_orders (self): return self.kapi.get_closed_orders()

    # Extras

    """
    Returns asset pairs with given quote currency. E.g. XXBTUSD quote is 'USD'
    
    From the whole set of asset pairs, filters the ones with given quote currency.
    
    Parameters
    ----------
    currency: the currency to look for
 
    """
    def asset_pairs_by_quote_currency (self, currency):
        ap =  self.asset_pairs()
        currencies = set(ap.loc['quote'])
        assert (currency in currencies), "currency must be one of {}".format(currencies)
        return ap.loc[:, ap.loc['quote'] == currency]