import os
import sys
from pydoc import locate

import click
import pkg_resources

import jesse.helpers as jh

# Hide the "FutureWarning: pandas.util.testing is deprecated." caused by empyrical
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

# Python version validation.
if jh.python_version() < 3.6:
    print(
        jh.color(
            'Jesse requires Python version above 3.6. Yours is {}'.format(jh.python_version()),
            'red'
        )
    )

# fix directory issue
sys.path.insert(0, os.getcwd())

ls = os.listdir('.')
is_jesse_project = 'strategies' in ls and 'config.py' in ls and 'storage' in ls and 'routes.py' in ls


def validate_cwd():
    """
    make sure we're in a Jesse project
    """
    if not is_jesse_project:
        print(
            jh.color(
                'Current directory is not a Jesse project. You must run commands from the root of a Jesse project.',
                'red'
            )
        )
        os._exit(1)


def inject_local_config():
    """
    injects config from local config file
    """
    local_config = locate('config.config')
    from jesse.config import set_config
    set_config(local_config)


def inject_local_routes():
    """
    injects routes from local routes folder
    """
    local_router = locate('routes')
    from jesse.routes import router

    router.set_routes(local_router.routes)
    router.set_extra_candles(local_router.extra_candles)


# inject local files
if is_jesse_project:
    inject_local_config()
    inject_local_routes()


def register_custom_exception_handler():
    """

    :return:
    """
    import sys
    import threading
    import traceback
    import logging
    from jesse.services import logger as jesse_logger
    import click
    from jesse import exceptions

    log_format = "%(message)s"

    if jh.is_livetrading():
        logging.basicConfig(filename='storage/logs/live-trade.txt', level=logging.INFO, filemode='w', format=log_format)
    elif jh.is_paper_trading():
        logging.basicConfig(filename='storage/logs/paper-trade.txt', level=logging.INFO, filemode='w',
                            format=log_format)
    elif jh.is_collecting_data():
        logging.basicConfig(filename='storage/logs/collect.txt', level=logging.INFO, filemode='w',
                            format=log_format)
    elif jh.is_optimizing():
        logging.basicConfig(filename='storage/logs/optimize.txt', level=logging.INFO, filemode='w',
                            format=log_format)
    else:
        logging.basicConfig(level=logging.INFO)

    # main thread
    def handle_exception(exc_type, exc_value, exc_traceback):
        """

        :param exc_type:
        :param exc_value:
        :param exc_traceback:
        :return:
        """
        if issubclass(exc_type, KeyboardInterrupt):
            sys.excepthook(exc_type, exc_value, exc_traceback)
            return

        # handle Breaking exceptions
        if exc_type in [
            exceptions.ConfigException, exceptions.RouteNotFound, exceptions.InvalidRoutes,
            exceptions.CandleNotFoundInDatabase
        ]:
            click.clear()
            print('=' * 30 + ' EXCEPTION TRACEBACK:')
            traceback.print_tb(exc_traceback, file=sys.stdout)
            print("=" * 73)
            print(
                '\n',
                jh.color('Uncaught Exception:', 'red'),
                jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
            )
            return

        # send notifications if it's a live session
        if jh.is_live():
            jesse_logger.error(
                '{}: {}'.format(exc_type.__name__, exc_value)
            )

        if jh.is_live() or jh.is_collecting_data():
            logging.error("Uncaught Exception:", exc_info=(exc_type, exc_value, exc_traceback))
        else:
            print('=' * 30 + ' EXCEPTION TRACEBACK:')
            traceback.print_tb(exc_traceback, file=sys.stdout)
            print("=" * 73)
            print(
                '\n',
                jh.color('Uncaught Exception:', 'red'),
                jh.color('{}: {}'.format(exc_type.__name__, exc_value), 'yellow')
            )

        if jh.is_paper_trading():
            print(
                jh.color(
                    'An uncaught exception was raised. Check the log file at:\n{}'.format(
                        'storage/logs/paper-trade.txt'
                    ),
                    'red'
                )
            )
        elif jh.is_livetrading():
            print(
                jh.color(
                    'An uncaught exception was raised. Check the log file at:\n{}'.format(
                        'storage/logs/live-trade.txt'
                    ),
                    'red'
                )
            )
        elif jh.is_collecting_data():
            print(
                jh.color(
                    'An uncaught exception was raised. Check the log file at:\n{}'.format(
                        'storage/logs/collect.txt'
                    ),
                    'red'
                )
            )

    sys.excepthook = handle_exception

    # other threads
    if jh.python_version() >= 3.8:
        def handle_thread_exception(args):
            """

            :param args:
            :return:
            """
            if args.exc_type == SystemExit:
                return

            # handle Breaking exceptions
            if args.exc_type in [
                exceptions.ConfigException, exceptions.RouteNotFound, exceptions.InvalidRoutes,
                exceptions.CandleNotFoundInDatabase
            ]:
                click.clear()
                print('=' * 30 + ' EXCEPTION TRACEBACK:')
                traceback.print_tb(args.exc_traceback, file=sys.stdout)
                print("=" * 73)
                print(
                    '\n',
                    jh.color('Uncaught Exception:', 'red'),
                    jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
                )
                return

            # send notifications if it's a live session
            if jh.is_live():
                jesse_logger.error(
                    '{}: {}'.format(args.exc_type.__name__, args.exc_value)
                )

            if jh.is_live() or jh.is_collecting_data():
                logging.error("Uncaught Exception:", exc_info=(args.exc_type, args.exc_value, args.exc_traceback))
            else:
                print('=' * 30 + ' EXCEPTION TRACEBACK:')
                traceback.print_tb(args.exc_traceback, file=sys.stdout)
                print("=" * 73)
                print(
                    '\n',
                    jh.color('Uncaught Exception:', 'red'),
                    jh.color('{}: {}'.format(args.exc_type.__name__, args.exc_value), 'yellow')
                )

            if jh.is_paper_trading():
                print(
                    jh.color(
                        'An uncaught exception was raised. Check the log file at:\n{}'.format(
                            'storage/logs/paper-trade.txt'
                        ),
                        'red'
                    )
                )
            elif jh.is_livetrading():
                print(
                    jh.color(
                        'An uncaught exception was raised. Check the log file at:\n{}'.format(
                            'storage/logs/live-trade.txt'
                        ),
                        'red'
                    )
                )
            elif jh.is_collecting_data():
                print(
                    jh.color(
                        'An uncaught exception was raised. Check the log file at:\n{}'.format(
                            'storage/logs/collect.txt'
                        ),
                        'red'
                    )
                )

        threading.excepthook = handle_thread_exception


# create a Click group
@click.group()
@click.version_option(pkg_resources.get_distribution("jesse").version)
def cli():
    """

    """
    pass


@cli.command()
@click.argument('exchange', required=True, type=str)
@click.argument('symbol', required=True, type=str)
@click.argument('start_date', required=True, type=str)
def import_candles(exchange, symbol, start_date):
    """
    imports historical candles from exchange
    """
    validate_cwd()
    from jesse.config import config
    config['app']['trading_mode'] = 'import-candles'

    register_custom_exception_handler()

    from jesse.services import db

    from jesse.modes import import_candles_mode

    import_candles_mode.run(exchange, symbol, start_date)

    db.close_connection()


@cli.command()
@click.argument('start_date', required=True, type=str)
@click.argument('finish_date', required=True, type=str)
@click.option('--debug/--no-debug', default=False,
              help='Displays logging messages instead of the progressbar. Used for debugging your strategy.')
@click.option('--csv/--no-csv', default=False,
              help='Outputs a CSV file of all executed trades on completion.')
@click.option('--json/--no-json', default=False,
              help='Outputs a JSON file of all executed trades on completion.')
@click.option('--fee/--no-fee', default=True, help='You can use "--no-fee" as a quick way to set trading fee to zero.')
@click.option('--chart/--no-chart', default=False,
              help='Generates charts of daily portfolio balance and assets price change. Useful for a visual comparision of your portfolio against the market.')
@click.option('--tradingview/--no-tradingview', default=False,
              help="Generates an output that can be copy-and-pasted into tradingview.com's pine-editor too see the trades in their charts.")
def backtest(start_date, finish_date, debug, csv, json, fee, chart, tradingview):
    """
    backtest mode. Enter in "YYYY-MM-DD" "YYYY-MM-DD"
    """
    validate_cwd()

    from jesse.config import config
    config['app']['trading_mode'] = 'backtest'

    register_custom_exception_handler()

    from jesse.services import db
    from jesse.modes import backtest_mode
    from jesse.services.selectors import get_exchange

    # debug flag
    config['app']['debug_mode'] = debug

    # fee flag
    if not fee:
        for e in config['app']['trading_exchanges']:
            config['env']['exchanges'][e]['fee'] = 0
            get_exchange(e).fee = 0

    backtest_mode.run(start_date, finish_date, chart=chart, tradingview=tradingview, csv=csv, json=json)

    db.close_connection()


@cli.command()
@click.argument('start_date', required=True, type=str)
@click.argument('finish_date', required=True, type=str)
@click.argument('optimal_total', required=True, type=int)
@click.option(
    '--debug/--no-debug', default=False,
    help='Displays detailed logs about the genetics algorithm. Use it if you are interested int he genetics algorithm.'
)
def optimize(start_date, finish_date, optimal_total, debug):
    """
    tunes the hyper-parameters of your strategy
    """
    validate_cwd()
    from jesse.config import config
    config['app']['trading_mode'] = 'optimize'

    register_custom_exception_handler()

    # debug flag
    config['app']['debug_mode'] = debug

    from jesse.modes.optimize_mode import optimize_mode

    optimize_mode(start_date, finish_date, optimal_total)


@cli.command()
@click.argument('name', required=True, type=str)
def make_strategy(name):
    """
    generates a new strategy folder from jesse/strategies/ExampleStrategy
    """
    validate_cwd()
    from jesse.config import config

    config['app']['trading_mode'] = 'make-strategy'

    register_custom_exception_handler()

    from jesse.services import strategy_maker

    strategy_maker.generate(name)


@cli.command()
@click.argument('name', required=True, type=str)
def make_project(name):
    """
    generates a new strategy folder from jesse/strategies/ExampleStrategy
    """
    from jesse.config import config

    config['app']['trading_mode'] = 'make-project'

    register_custom_exception_handler()

    from jesse.services import project_maker

    project_maker.generate(name)


@cli.command()
@click.option('--dna/--no-dna', default=False, help='Translates DNA into parameters. Used in optimize mode only')
def routes(dna):
    """
    lists all routes
    """
    validate_cwd()
    from jesse.config import config

    config['app']['trading_mode'] = 'routes'

    register_custom_exception_handler()

    from jesse.modes import routes_mode

    routes_mode.run(dna)


if 'plugins' in ls:
    @cli.command()
    def collect():
        """
        fetches streamed market data such as tickers, trades, and orderbook from
        the WS connection and stores them into the database for later research.
        """
        validate_cwd()

        # set trading mode
        from jesse.config import config
        config['app']['trading_mode'] = 'collect'

        register_custom_exception_handler()

        from plugins.live.collect_mode import run

        run()


    @cli.command()
    @click.option('--testdrive/--no-testdrive', default=False)
    @click.option('--debug/--no-debug', default=False)
    @click.option('--dev/--no-dev', default=False)
    @click.option('--fee/--no-fee', default=True)
    def live(testdrive, debug, dev, fee):
        """
        trades in real-time on exchange with REAL money
        """
        validate_cwd()

        # set trading mode
        from jesse.config import config
        config['app']['trading_mode'] = 'livetrade'
        config['app']['is_test_driving'] = testdrive

        register_custom_exception_handler()

        # debug flag
        config['app']['debug_mode'] = debug

        from plugins.live import init
        from jesse.services.selectors import get_exchange

        # fee flag
        if not fee:
            for e in config['app']['trading_exchanges']:
                config['env']['exchanges'][e]['fee'] = 0
                get_exchange(e).fee = 0

        # inject live config
        init(config)

        # execute live session
        from plugins.live.live_mode import run
        run(dev)


    @cli.command()
    @click.option('--debug/--no-debug', default=False)
    @click.option('--dev/--no-dev', default=False)
    @click.option('--fee/--no-fee', default=True)
    def paper(debug, dev, fee):
        """
        trades in real-time on exchange with PAPER money
        """
        validate_cwd()

        # set trading mode
        from jesse.config import config
        config['app']['trading_mode'] = 'papertrade'

        register_custom_exception_handler()

        # debug flag
        config['app']['debug_mode'] = debug

        from plugins.live import init
        from jesse.services.selectors import get_exchange

        # fee flag
        if not fee:
            for e in config['app']['trading_exchanges']:
                config['env']['exchanges'][e]['fee'] = 0
                get_exchange(e).fee = 0

        # inject live config
        init(config)

        # execute live session
        from plugins.live.live_mode import run
        run(dev)
