import time
import requests
import logging
import json
from twisted.internet import defer, threads, reactor
from twisted.internet.task import LoopingCall

from lbrynet import conf
from lbrynet.metadata.Fee import FeeValidator
from lbrynet.core.Error import InvalidExchangeRateResponse

log = logging.getLogger(__name__)

CURRENCY_PAIRS = ["USDBTC", "BTCLBC"]
BITTREX_FEE = 0.0025
COINBASE_FEE = 0.0 #add fee


class ExchangeRate(object):
    def __init__(self, market, spot, ts):
        assert int(time.time()) - ts < 600
        self.currency_pair = (market[0:3], market[3:6])
        self.spot = spot
        self.ts = ts

    def __repr__(self):
        out = "Currency pair:{}, spot:{}, ts:{}".format(
            self.currency_pair, self.spot, self.ts)
        return out

    def as_dict(self):
        return {'spot': self.spot, 'ts': self.ts}


class MarketFeed(object):
    REQUESTS_TIMEOUT = 20
    EXCHANGE_RATE_UPDATE_RATE_SEC = 300
    def __init__(self, market, name, url, params, fee):
        self.market = market
        self.name = name
        self.url = url
        self.params = params
        self.fee = fee
        self.rate = None
        self._updater = LoopingCall(self._update_price)

    @property
    def rate_is_initialized(self):
        return self.rate is not None

    def _make_request(self):
        r = requests.get(self.url, self.params, timeout=self.REQUESTS_TIMEOUT)
        return r.text

    def _handle_response(self, response):
        return NotImplementedError

    def _subtract_fee(self, from_amount):
        # increase amount to account for market fees
        return defer.succeed(from_amount / (1.0 - self.fee))

    def _save_price(self, price):
        log.debug("Saving price update %f for %s" % (price, self.market))
        self.rate = ExchangeRate(self.market, price, int(time.time()))

    def _log_error(self, err):
        log.error(err)
        log.warning(
            "There was a problem updating %s exchange rate information from %s",
            self.market, self.name)

    def _update_price(self):
        d = threads.deferToThread(self._make_request)
        d.addCallback(self._handle_response)
        d.addCallback(self._subtract_fee)
        d.addCallback(self._save_price)
        d.addErrback(self._log_error)
        return d

    def start(self):
        if not self._updater.running:
            self._updater.start(self.EXCHANGE_RATE_UPDATE_RATE_SEC)

    def stop(self):
        if self._updater.running:
            self._updater.stop()


class BittrexFeed(MarketFeed):
    def __init__(self):
        MarketFeed.__init__(
            self,
            "BTCLBC",
            "Bittrex",
            conf.settings['bittrex_feed'],
            {'market': 'BTC-LBC', 'count': 50},
            BITTREX_FEE
        )

    def _handle_response(self, response):
        json_response = json.loads(response)
        if 'result' not in json_response:
            raise InvalidExchangeRateResponse(self.name, 'result not found')
        trades = json_response['result']
        if len(trades) == 0:
            raise InvalidExchangeRateResponse(self.market, 'trades not found')
        totals = sum([i['Total'] for i in trades])
        qtys = sum([i['Quantity'] for i in trades])
        if totals <= 0 or qtys <= 0:
            raise InvalidExchangeRateResponse(self.market, 'quantities were not positive')
        vwap = totals/qtys
        return defer.succeed(float(1.0 / vwap))


class GoogleBTCFeed(MarketFeed):
    def __init__(self):
        MarketFeed.__init__(
            self,
            "USDBTC",
            "Coinbase via Google finance",
            'http://finance.google.com/finance/info',
            {'client':'ig', 'q':'CURRENCY:USDBTC'},
            COINBASE_FEE
        )

    def _handle_response(self, response):
        response = response[3:] # response starts with "// "
        json_response = json.loads(response)[0]
        if 'l' not in json_response:
            raise InvalidExchangeRateResponse(self.name, 'last trade not found')
        last_trade_price = float(json_response['l'])
        if last_trade_price <= 0:
            raise InvalidExchangeRateResponse(self.name, 'trade price was not positive')
        return defer.succeed(last_trade_price)


def get_default_market_feed(currency_pair):
    currencies = None
    if isinstance(currency_pair, str):
        currencies = (currency_pair[0:3], currency_pair[3:6])
    elif isinstance(currency_pair, tuple):
        currencies = currency_pair
    assert currencies is not None

    if currencies == ("USD", "BTC"):
        return GoogleBTCFeed()
    elif currencies == ("BTC", "LBC"):
        return BittrexFeed()


class ExchangeRateManager(object):
    def __init__(self):
        reactor.addSystemEventTrigger('before', 'shutdown', self.stop)
        self.market_feeds = [
            get_default_market_feed(currency_pair) for currency_pair in CURRENCY_PAIRS]

    def start(self):
        log.info("Starting exchange rate manager")
        for feed in self.market_feeds:
            feed.start()

    def stop(self):
        log.info("Stopping exchange rate manager")
        for source in self.market_feeds:
            source.stop()

    def convert_currency(self, from_currency, to_currency, amount):
        rates = [market.rate for market in self.market_feeds]
        log.info("Converting %f %s to %s, rates: %s" % (amount, from_currency, to_currency, rates))
        if from_currency == to_currency:
            return amount
        for market in self.market_feeds:
            if (market.rate_is_initialized and
                market.rate.currency_pair == (from_currency, to_currency)):
                return amount * market.rate.spot
        for market in self.market_feeds:
            if (market.rate_is_initialized and
                market.rate.currency_pair[0] == from_currency):
                return self.convert_currency(
                    market.rate.currency_pair[1], to_currency, amount * market.rate.spot)
        raise Exception(
            'Unable to convert {} from {} to {}'.format(amount, from_currency, to_currency))

    def fee_dict(self):
        return {market: market.rate.as_dict() for market in self.market_feeds}

    def to_lbc(self, fee):
        if fee is None:
            return None
        if not isinstance(fee, FeeValidator):
            fee_in = FeeValidator(fee)
        else:
            fee_in = fee

        return FeeValidator({
            fee_in.currency_symbol: {
                'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
                'address': fee_in.address
            }
        })


class DummyBTCLBCFeed(MarketFeed):
    def __init__(self):
        MarketFeed.__init__(
            self,
            "BTCLBC",
            "market name",
            "derp.com",
            None,
            0.0
        )


class DummyUSDBTCFeed(MarketFeed):
    def __init__(self):
        MarketFeed.__init__(
            self,
            "USDBTC",
            "market name",
            "derp.com",
            None,
            0.0
        )


class DummyExchangeRateManager(object):
    def __init__(self, rates):
        self.market_feeds = [DummyBTCLBCFeed(), DummyUSDBTCFeed()]
        for feed in self.market_feeds:
            feed.rate = ExchangeRate(
                feed.market, rates[feed.market]['spot'], rates[feed.market]['ts'])

    def convert_currency(self, from_currency, to_currency, amount):
        log.debug("Converting %f %s to %s" % (amount, from_currency, to_currency))
        for market in self.market_feeds:
            if (market.rate_is_initialized and
                market.rate.currency_pair == (from_currency, to_currency)):
                return amount * market.rate.spot
        for market in self.market_feeds:
            if (market.rate_is_initialized and
                market.rate.currency_pair[0] == from_currency):
                return self.convert_currency(
                    market.rate.currency_pair[1], to_currency, amount * market.rate.spot)

    def to_lbc(self, fee):
        if fee is None:
            return None
        if not isinstance(fee, FeeValidator):
            fee_in = FeeValidator(fee)
        else:
            fee_in = fee

        return FeeValidator({
            fee_in.currency_symbol: {
                'amount': self.convert_currency(fee_in.currency_symbol, "LBC", fee_in.amount),
                'address': fee_in.address
            }
        })