import random

import os
import pandas as pd
from datetime import timedelta
from logbook import TestHandler
from pandas.util.testing import assert_frame_equal

from catalyst import get_calendar
from catalyst.exchange.exchange_asset_finder import ExchangeAssetFinder
from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest
from catalyst.exchange.utils.exchange_utils import get_candles_df
from catalyst.exchange.utils.factory import get_exchange
from catalyst.exchange.utils.test_utils import output_df, \
    select_random_assets
from catalyst.exchange.utils.stats_utils import set_print_settings

pd.set_option('display.expand_frame_repr', False)
pd.set_option('precision', 8)
pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 1000)


class TestSuiteBundle:
    @staticmethod
    def get_data_portal(exchanges):
        open_calendar = get_calendar('OPEN')
        asset_finder = ExchangeAssetFinder(exchanges)

        exchange_names = [exchange.name for exchange in exchanges]
        data_portal = DataPortalExchangeBacktest(
            exchange_names=exchange_names,
            asset_finder=asset_finder,
            trading_calendar=open_calendar,
            first_trading_day=None  # will set dynamically based on assets
        )
        return data_portal

    def compare_bundle_with_exchange(self, exchange, assets, end_dt, bar_count,
                                     freq, data_frequency, data_portal, field):
        """
        Creates DataFrames from the bundle and exchange for the specified
        data set.

        Parameters
        ----------
        exchange: Exchange
        assets
        end_dt
        bar_count
        freq
        data_frequency
        data_portal

        Returns
        -------

        """
        data = dict()

        log_catcher = TestHandler()
        with log_catcher:
            symbols = [asset.symbol for asset in assets]
            print(
                'comparing {} for {}/{} with {} timeframe until {}'.format(
                    field, exchange.name, symbols, freq, end_dt
                )
            )
            data['bundle'] = data_portal.get_history_window(
                assets=assets,
                end_dt=end_dt,
                bar_count=bar_count,
                frequency=freq,
                field=field,
                data_frequency=data_frequency,
            )
            set_print_settings()
            print(
                'the bundle data:\n{}'.format(
                    data['bundle']
                )
            )
            candles = exchange.get_candles(
                end_dt=end_dt,
                freq=freq,
                assets=assets,
                bar_count=bar_count,
            )
            data['exchange'] = get_candles_df(
                candles=candles,
                field=field,
                freq=freq,
                bar_count=bar_count,
                end_dt=end_dt,
            )
            print(
                'the exchange data:\n{}'.format(
                    data['exchange']
                )
            )
            for source in data:
                df = data[source]
                path, folder = output_df(
                    df, assets, '{}_{}'.format(freq, source)
                )

            print('saved {} test results: {}'.format(end_dt, folder))

            assert_frame_equal(
                right=data['bundle'][:-1],
                left=data['exchange'][:-1],
                check_less_precise=1,
            )
            try:
                assert_frame_equal(
                    right=data['bundle'][:-1],
                    left=data['exchange'][:-1],
                    check_less_precise=min([a.decimals for a in assets]),
                )
            except Exception as e:
                print(
                    'Some differences were found within a 1 decimal point '
                    'interval of confidence: {}'.format(e)
                )
                with open(os.path.join(folder, 'compare.txt'), 'w+') as handle:
                    handle.write(e.args[0])

            pass

    def compare_current_with_last_candle(self, exchange, assets, end_dt,
                                         freq, data_frequency, data_portal):
        """
        Creates DataFrames from the bundle and exchange for the specified
        data set.

        Parameters
        ----------
        exchange: Exchange
        assets
        end_dt
        bar_count
        freq
        data_frequency
        data_portal

        Returns
        -------

        """
        data = dict()

        assets = sorted(assets, key=lambda a: a.symbol)
        log_catcher = TestHandler()
        with log_catcher:
            symbols = [asset.symbol for asset in assets]
            print(
                'comparing data for {}/{} with {} timeframe on {}'.format(
                    exchange.name, symbols, freq, end_dt
                )
            )
            data['candle'] = data_portal.get_history_window(
                assets=assets,
                end_dt=end_dt,
                bar_count=1,
                frequency=freq,
                field='close',
                data_frequency=data_frequency,
            )
            set_print_settings()
            print(
                'the bundle first / last row:\n{}'.format(
                    data['candle'].iloc[[-1]]
                )
            )
            current = data_portal.get_spot_value(
                assets=assets,
                field='close',
                dt=end_dt,
                data_frequency=data_frequency,
            )
            data['current'] = pd.Series(data=current, index=assets)
            print(
                'the current price:\n{}'.format(
                    data['current']
                )
            )
            pass

    def test_validate_bundles(self):
        # exchange_population = 3
        asset_population = 3
        data_frequency = random.choice(['minute'])

        # bundle = 'dailyBundle' if data_frequency
        #  == 'daily' else 'minuteBundle'
        # exchanges = select_random_exchanges(
        #     population=exchange_population,
        #     features=[bundle],
        # )  # Type: list[Exchange]
        exchanges = [get_exchange('poloniex', skip_init=True)]

        data_portal = TestSuiteBundle.get_data_portal(exchanges)
        for exchange in exchanges:
            exchange.init()

            frequencies = exchange.get_candle_frequencies(data_frequency)
            freq = random.sample(frequencies, 1)[0]
            rnd = random.SystemRandom()
            # field = rnd.choice(['open', 'high', 'low', 'close', 'volume'])
            field = rnd.choice(['volume'])

            bar_count = random.randint(3, 6)

            assets = select_random_assets(
                exchange.assets, asset_population
            )
            end_dt = None
            for asset in assets:
                attribute = 'end_{}'.format(data_frequency)
                asset_end_dt = getattr(asset, attribute)

                if end_dt is None or asset_end_dt < end_dt:
                    end_dt = asset_end_dt

            end_dt = end_dt + timedelta(minutes=3)
            dt_range = pd.date_range(
                end=end_dt, periods=bar_count, freq=freq
            )
            self.compare_bundle_with_exchange(
                exchange=exchange,
                assets=assets,
                end_dt=dt_range[-1],
                bar_count=bar_count,
                freq=freq,
                data_frequency=data_frequency,
                data_portal=data_portal,
                field=field,
            )
        pass

    def test_validate_last_candle(self):
        # exchange_population = 3
        asset_population = 3
        data_frequency = random.choice(['minute'])

        # bundle = 'dailyBundle' if data_frequency
        #  == 'daily' else 'minuteBundle'
        # exchanges = select_random_exchanges(
        #     population=exchange_population,
        #     features=[bundle],
        # )  # Type: list[Exchange]
        exchanges = [get_exchange('poloniex', skip_init=True)]

        data_portal = TestSuiteBundle.get_data_portal(exchanges)
        for exchange in exchanges:
            exchange.init()

            frequencies = exchange.get_candle_frequencies(data_frequency)
            freq = random.sample(frequencies, 1)[0]

            assets = select_random_assets(
                exchange.assets, asset_population
            )
            end_dt = None
            for asset in assets:
                attribute = 'end_{}'.format(data_frequency)
                asset_end_dt = getattr(asset, attribute)

                if end_dt is None or asset_end_dt < end_dt:
                    end_dt = asset_end_dt

            end_dt = end_dt + timedelta(minutes=3)
            self.compare_current_with_last_candle(
                exchange=exchange,
                assets=assets,
                end_dt=end_dt,
                freq=freq,
                data_frequency=data_frequency,
                data_portal=data_portal,
            )
        pass
