# pragma pylint: disable=missing-docstring, C0103
import argparse
from pathlib import Path
from unittest.mock import MagicMock

import pytest

from freqtrade.commands import Arguments
from freqtrade.commands.cli_options import check_int_nonzero, check_int_positive
from tests.conftest import CURRENT_TEST_STRATEGY


# Parse common command-line-arguments. Used for all tools
def test_parse_args_none() -> None:
    arguments = Arguments(['trade'])
    assert isinstance(arguments, Arguments)
    x = arguments.get_parsed_arg()
    assert isinstance(x, dict)
    assert isinstance(arguments.parser, argparse.ArgumentParser)


def test_parse_args_defaults(mocker) -> None:
    mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
    args = Arguments(['trade']).get_parsed_arg()
    assert args['config'] == ['config.json']
    assert args['strategy_path'] is None
    assert args['datadir'] is None
    assert args['verbosity'] == 0


def test_parse_args_default_userdatadir(mocker) -> None:
    mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
    args = Arguments(['trade']).get_parsed_arg()
    # configuration defaults to user_data if that is available.
    assert args['config'] == [str(Path('user_data/config.json'))]
    assert args['strategy_path'] is None
    assert args['datadir'] is None
    assert args['verbosity'] == 0


def test_parse_args_userdatadir(mocker) -> None:
    mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
    args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg()
    # configuration defaults to user_data if that is available.
    assert args['config'] == [str(Path('user_data/config.json'))]
    assert args['strategy_path'] is None
    assert args['datadir'] is None
    assert args['verbosity'] == 0


def test_parse_args_config() -> None:
    args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
    assert args['config'] == ['/dev/null']

    args = Arguments(['trade', '--config', '/dev/null']).get_parsed_arg()
    assert args['config'] == ['/dev/null']

    args = Arguments(['trade', '--config', '/dev/null',
                      '--config', '/dev/zero'],).get_parsed_arg()
    assert args['config'] == ['/dev/null', '/dev/zero']


def test_parse_args_db_url() -> None:
    args = Arguments(['trade', '--db-url', 'sqlite:///test.sqlite']).get_parsed_arg()
    assert args['db_url'] == 'sqlite:///test.sqlite'


def test_parse_args_verbose() -> None:
    args = Arguments(['trade', '-v']).get_parsed_arg()
    assert args['verbosity'] == 1

    args = Arguments(['trade', '--verbose']).get_parsed_arg()
    assert args['verbosity'] == 1


def test_common_scripts_options() -> None:
    args = Arguments(['download-data', '-p', 'ETH/BTC', 'XRP/BTC']).get_parsed_arg()

    assert args['pairs'] == ['ETH/BTC', 'XRP/BTC']
    assert 'func' in args


def test_parse_args_version() -> None:
    with pytest.raises(SystemExit, match=r'0'):
        Arguments(['--version']).get_parsed_arg()


def test_parse_args_invalid() -> None:
    with pytest.raises(SystemExit, match=r'2'):
        Arguments(['-c']).get_parsed_arg()


def test_parse_args_strategy() -> None:
    args = Arguments(['trade', '--strategy', 'SomeStrategy']).get_parsed_arg()
    assert args['strategy'] == 'SomeStrategy'


def test_parse_args_strategy_invalid() -> None:
    with pytest.raises(SystemExit, match=r'2'):
        Arguments(['--strategy']).get_parsed_arg()


def test_parse_args_strategy_path() -> None:
    args = Arguments(['trade', '--strategy-path', '/some/path']).get_parsed_arg()
    assert args['strategy_path'] == '/some/path'


def test_parse_args_strategy_path_invalid() -> None:
    with pytest.raises(SystemExit, match=r'2'):
        Arguments(['--strategy-path']).get_parsed_arg()


def test_parse_args_backtesting_invalid() -> None:
    with pytest.raises(SystemExit, match=r'2'):
        Arguments(['backtesting --timeframe']).get_parsed_arg()

    with pytest.raises(SystemExit, match=r'2'):
        Arguments(['backtesting --timeframe', 'abc']).get_parsed_arg()


def test_parse_args_backtesting_custom() -> None:
    args = [
        'backtesting',
        '-c', 'test_conf.json',
        '--timeframe', '1m',
        '--strategy-list',
        CURRENT_TEST_STRATEGY,
        'SampleStrategy'
    ]
    call_args = Arguments(args).get_parsed_arg()
    assert call_args['config'] == ['test_conf.json']
    assert call_args['verbosity'] == 0
    assert call_args['command'] == 'backtesting'
    assert call_args['func'] is not None
    assert call_args['timeframe'] == '1m'
    assert type(call_args['strategy_list']) is list
    assert len(call_args['strategy_list']) == 2


def test_parse_args_hyperopt_custom() -> None:
    args = [
        'hyperopt',
        '-c', 'test_conf.json',
        '--epochs', '20',
        '--spaces', 'buy'
    ]
    call_args = Arguments(args).get_parsed_arg()
    assert call_args['config'] == ['test_conf.json']
    assert call_args['epochs'] == 20
    assert call_args['verbosity'] == 0
    assert call_args['command'] == 'hyperopt'
    assert call_args['spaces'] == ['buy']
    assert call_args['func'] is not None
    assert callable(call_args['func'])


def test_download_data_options() -> None:
    args = [
        'download-data',
        '--datadir', 'datadir/directory',
        '--pairs-file', 'file_with_pairs',
        '--days', '30',
        '--exchange', 'binance'
    ]
    pargs = Arguments(args).get_parsed_arg()

    assert pargs['pairs_file'] == 'file_with_pairs'
    assert pargs['datadir'] == 'datadir/directory'
    assert pargs['days'] == 30
    assert pargs['exchange'] == 'binance'


def test_plot_dataframe_options() -> None:
    args = [
        'plot-dataframe',
        '-c', 'config_examples/config_bittrex.example.json',
        '--indicators1', 'sma10', 'sma100',
        '--indicators2', 'macd', 'fastd', 'fastk',
        '--plot-limit', '30',
        '-p', 'UNITTEST/BTC',
    ]
    pargs = Arguments(args).get_parsed_arg()

    assert pargs['indicators1'] == ['sma10', 'sma100']
    assert pargs['indicators2'] == ['macd', 'fastd', 'fastk']
    assert pargs['plot_limit'] == 30
    assert pargs['pairs'] == ['UNITTEST/BTC']


@pytest.mark.parametrize('auto_open_arg', [True, False])
def test_plot_profit_options(auto_open_arg: bool) -> None:
    args = [
        'plot-profit',
        '-p', 'UNITTEST/BTC',
        '--trade-source', 'DB',
        '--db-url', 'sqlite:///whatever.sqlite',
    ]
    if auto_open_arg:
        args.append('--auto-open')
    pargs = Arguments(args).get_parsed_arg()

    assert pargs['trade_source'] == 'DB'
    assert pargs['pairs'] == ['UNITTEST/BTC']
    assert pargs['db_url'] == 'sqlite:///whatever.sqlite'
    assert pargs['plot_auto_open'] == auto_open_arg


def test_config_notallowed(mocker) -> None:
    mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
    args = [
        'create-userdir',
    ]
    pargs = Arguments(args).get_parsed_arg()

    assert 'config' not in pargs

    # When file exists:
    mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
    args = [
        'create-userdir',
    ]
    pargs = Arguments(args).get_parsed_arg()
    # config is not added even if it exists, since create-userdir is in the notallowed list
    assert 'config' not in pargs


def test_config_notrequired(mocker) -> None:
    mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
    args = [
        'download-data',
    ]
    pargs = Arguments(args).get_parsed_arg()

    assert pargs['config'] is None

    # When file exists:
    mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
    args = [
        'download-data',
    ]
    pargs = Arguments(args).get_parsed_arg()
    # config is added if it exists
    assert pargs['config'] == ['config.json']


def test_check_int_positive() -> None:
    assert check_int_positive('3') == 3
    assert check_int_positive('1') == 1
    assert check_int_positive('100') == 100

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_positive('-2')

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_positive('0')

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_positive(0)

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_positive('3.5')

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_positive('DeadBeef')


def test_check_int_nonzero() -> None:
    assert check_int_nonzero('3') == 3
    assert check_int_nonzero('1') == 1
    assert check_int_nonzero('100') == 100

    assert check_int_nonzero('-2') == -2

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_nonzero('0')

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_nonzero(0)

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_nonzero('3.5')

    with pytest.raises(argparse.ArgumentTypeError):
        check_int_nonzero('DeadBeef')