import os
import time
import dateparser
import pytz
import json
import pandas as pd
from datetime import timezone
from datetime import datetime
from binance.client import Client

pd.set_option('display.max_columns',None)

dir = './data'
pd.set_option('display.max_columns',None)
if not os.path.isdir(dir):
    os.mkdir(dir)

class BinanceSDK():

    def get_orderbook(self, symbol, limit=100):
        client = Client("", "")
        data = client.get_order_book(
            symbol=symbol,
            limit=limit
        )
        df = pd.DataFrame(data['bids'], columns=['bids_price', 'bids_qty'])
        df1 = pd.DataFrame(data['asks'], columns=['asks_price', 'asks_qty'])
        mergeddf = df.merge(df1, left_index=True, right_index=True)
        mergeddf.to_csv(dir + '/orderbook.csv')
        return mergeddf

    def get_recent_trades(self, symbol):
        client = Client("", "")
        temp_data = client.get_recent_trades(
            symbol=symbol
        )
        df1 = pd.DataFrame(temp_data, columns=['id', 'price', 'qty', 'quoteQty', 'time', 'isBuyerMaker', 'isBestMatch'])
        df1['time'] = pd.to_datetime(df1['time'], unit='ms')
        df1.to_csv(dir + '/recent_trades.csv')
        return df1

    def date_to_milliseconds(self, date_str):
        """Convert UTC date to milliseconds

        If using offset strings add "UTC" to date string e.g. "now UTC", "11 hours ago UTC"

        See dateparse docs for formats http://dateparser.readthedocs.io/en/latest/

        :param date_str: date in readable format, i.e. "January 01, 2018", "11 hours ago UTC", "now UTC"
        :type date_str: str
        """
        # get epoch value in UTC
        epoch = datetime.utcfromtimestamp(0).replace(tzinfo=pytz.utc)
        # parse our date string
        d = dateparser.parse(date_str)
        # if the date is not timezone aware apply UTC timezone
        if d.tzinfo is None or d.tzinfo.utcoffset(d) is None:
            d = d.replace(tzinfo=pytz.utc)

        # return the difference in time
        return int((d - epoch).total_seconds() * 1000.0)

    def interval_to_milliseconds(self, interval):
        """Convert a Binance interval string to milliseconds

        :param interval: Binance interval string 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w
        :type interval: str

        :return:
             None if unit not one of m, h, d or w
             None if string not in correct format
             int value of interval in milliseconds
        """
        ms = None
        seconds_per_unit = {
            "m": 60,
            "h": 60 * 60,
            "d": 24 * 60 * 60,
            "w": 7 * 24 * 60 * 60
        }

        unit = interval[-1]
        if unit in seconds_per_unit:
            try:
                ms = int(interval[:-1]) * seconds_per_unit[unit] * 1000
            except ValueError:
                pass
        return ms

    def get_historical_klines(self, symbol, interval, start_str, end_str=None):
        """Get Historical Klines from Binance

        See dateparse docs for valid start and end string formats http://dateparser.readthedocs.io/en/latest/

        If using offset strings for dates add "UTC" to date string e.g. "now UTC", "11 hours ago UTC"

        :param symbol: Name of symbol pair e.g BNBBTC
        :type symbol: str
        :param interval: Biannce Kline interval
        :type interval: str
        :param start_str: Start date string in UTC format
        :type start_str: str
        :param end_str: optional - end date string in UTC format
        :type end_str: str

        :return: list of OHLCV values

        """
        # create the Binance client, no need for api key
        client = Client("", "")

        # init our list
        output_data = []

        # setup the max limit
        limit = 500

        # convert interval to useful value in seconds
        a = BinanceSDK()
        timeframe = a.interval_to_milliseconds(interval)

        # convert our date strings to milliseconds
        start_ts = a.date_to_milliseconds(start_str)

        # if an end time was passed convert it
        end_ts = None
        if end_str:
            end_ts = a.date_to_milliseconds(end_str)

        idx = 0
        # it can be difficult to know when a symbol was listed on Binance so allow start time to be before list date
        symbol_existed = False
        while True:
            # fetch the klines from start_ts up to max 500 entries or the end_ts if set
            temp_data = client.get_klines(
                symbol=symbol,
                interval=interval,
                limit=limit,
                startTime=start_ts,
                endTime=end_ts
            )

            # handle the case where our start date is before the symbol pair listed on Binance
            if not symbol_existed and len(temp_data):
                symbol_existed = True

            if symbol_existed:
                # append this loops data to our output data
                output_data += temp_data

                # update our start timestamp using the last value in the array and add the interval timeframe
                start_ts = temp_data[len(temp_data) - 1][0] + timeframe
            else:
                # it wasn't listed yet, increment our start date
                start_ts += timeframe

            idx += 1
            # check if we received less than the required limit and exit the loop
            if len(temp_data) < limit:
                # exit the while loop
                break

            # sleep after every 3rd call to be kind to the API
            if idx % 3 == 0:
                time.sleep(1)
        df = pd.DataFrame(output_data,
                          columns=['Open_Time', 'Open', 'High', 'Low', 'Close', 'Volume', 'Close_Time',
                                   'Quote_Asset_Volume',
                                   'Num_Trades', 'Tb_Base_Asset_Volume', 'Tb_Quote_Asset_Volume', 'Ignored'])
        df['Open_Time'] = pd.to_datetime(df['Open_Time'], unit='ms')
        df['Close_Time'] = pd.to_datetime(df['Close_Time'], unit='ms')
        df.to_csv(dir + '/historical_klines.csv')
        return df



A = BinanceSDK()

A.get_historical_klines("BTCUSDT", '1h', "2019-1-1")
A.get_recent_trades("BTCUSDT")
A.get_orderbook("BTCUSDT", 500)