import logging
from unittest.mock import MagicMock, PropertyMock

import pandas as pd
import pytest

from freqtrade.commands.analyze_commands import start_analysis_entries_exits
from freqtrade.commands.optimize_commands import start_backtesting
from freqtrade.enums import ExitType
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.backtesting import Backtesting
from tests.conftest import get_args, patch_exchange, patched_configuration_load_config_file


@pytest.fixture(autouse=True)
def entryexitanalysis_cleanup() -> None:
    yield None

    Backtesting.cleanup()


def test_backtest_analysis_on_entry_and_rejected_signals_nomock(
    default_conf, mocker, caplog, testdatadir, user_dir, capsys
):
    caplog.set_level(logging.INFO)
    (user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)

    default_conf.update(
        {
            "use_exit_signal": True,
            "exit_profit_only": False,
            "exit_profit_offset": 0.0,
            "ignore_roi_if_entry_signal": False,
        }
    )
    patch_exchange(mocker)
    result1 = pd.DataFrame(
        {
            "pair": ["ETH/BTC", "LTC/BTC", "ETH/BTC", "LTC/BTC"],
            "profit_ratio": [0.025, 0.05, -0.1, -0.05],
            "profit_abs": [0.5, 2.0, -4.0, -2.0],
            "open_date": pd.to_datetime(
                [
                    "2018-01-29 18:40:00",
                    "2018-01-30 03:30:00",
                    "2018-01-30 08:10:00",
                    "2018-01-31 13:30:00",
                ],
                utc=True,
            ),
            "close_date": pd.to_datetime(
                [
                    "2018-01-29 20:45:00",
                    "2018-01-30 05:35:00",
                    "2018-01-30 09:10:00",
                    "2018-01-31 15:00:00",
                ],
                utc=True,
            ),
            "trade_duration": [235, 40, 60, 90],
            "is_open": [False, False, False, False],
            "stake_amount": [0.01, 0.01, 0.01, 0.01],
            "open_rate": [0.104445, 0.10302485, 0.10302485, 0.10302485],
            "close_rate": [0.104969, 0.103541, 0.102041, 0.102541],
            "is_short": [False, False, False, False],
            "enter_tag": [
                "enter_tag_long_a",
                "enter_tag_long_b",
                "enter_tag_long_a",
                "enter_tag_long_b",
            ],
            "exit_reason": [
                ExitType.ROI.value,
                ExitType.EXIT_SIGNAL.value,
                ExitType.STOP_LOSS.value,
                ExitType.TRAILING_STOP_LOSS.value,
            ],
        }
    )

    backtestmock = MagicMock(
        side_effect=[
            {
                "results": result1,
                "config": default_conf,
                "locks": [],
                "rejected_signals": 20,
                "timedout_entry_orders": 0,
                "timedout_exit_orders": 0,
                "canceled_trade_entries": 0,
                "canceled_entry_orders": 0,
                "replaced_entry_orders": 0,
                "final_balance": 1000,
            }
        ]
    )
    mocker.patch(
        "freqtrade.plugins.pairlistmanager.PairListManager.whitelist",
        PropertyMock(return_value=["ETH/BTC", "LTC/BTC", "DASH/BTC"]),
    )
    mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock)

    patched_configuration_load_config_file(mocker, default_conf)

    args = [
        "backtesting",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
        "--timeframe",
        "5m",
        "--timerange",
        "1515560100-1517287800",
        "--export",
        "signals",
        "--cache",
        "none",
    ]
    args = get_args(args)
    start_backtesting(args)

    captured = capsys.readouterr()
    assert "BACKTESTING REPORT" in captured.out
    assert "EXIT REASON STATS" in captured.out
    assert "LEFT OPEN TRADES REPORT" in captured.out

    base_args = [
        "backtesting-analysis",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
    ]

    # test group 0 and indicator list
    args = get_args(
        [*base_args, "--analysis-groups", "0", "--indicator-list", "close", "rsi", "profit_abs"]
    )
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "LTC/BTC" in captured.out
    assert "ETH/BTC" in captured.out
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "exit_signal" in captured.out
    assert "roi" in captured.out
    assert "stop_loss" in captured.out
    assert "trailing_stop_loss" in captured.out
    assert "0.5" in captured.out
    assert "-4" in captured.out
    assert "-2" in captured.out
    assert "-3.5" in captured.out
    assert "50" in captured.out
    assert "0" in captured.out
    assert "0.016" in captured.out
    assert "34.049" in captured.out
    assert "0.104" in captured.out
    assert "52.829" in captured.out
    # assert indicator list
    assert "close (entry)" in captured.out
    assert "0.016" in captured.out
    assert "rsi (entry)" in captured.out
    assert "54.320" in captured.out
    assert "close (exit)" in captured.out
    assert "rsi (exit)" in captured.out
    assert "52.829" in captured.out
    assert "profit_abs" in captured.out

    # test group 1
    args = get_args([*base_args, "--analysis-groups", "1"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "total_profit_pct" in captured.out
    assert "-3.5" in captured.out
    assert "-1.75" in captured.out
    assert "-7.5" in captured.out
    assert "-3.75" in captured.out
    assert "0" in captured.out

    # test group 2
    args = get_args([*base_args, "--analysis-groups", "2"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "exit_signal" in captured.out
    assert "roi" in captured.out
    assert "stop_loss" in captured.out
    assert "trailing_stop_loss" in captured.out
    assert "total_profit_pct" in captured.out
    assert "-10" in captured.out
    assert "-5" in captured.out
    assert "2.5" in captured.out

    # test group 3
    args = get_args([*base_args, "--analysis-groups", "3"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "LTC/BTC" in captured.out
    assert "ETH/BTC" in captured.out
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "total_profit_pct" in captured.out
    assert "-7.5" in captured.out
    assert "-3.75" in captured.out
    assert "-1.75" in captured.out
    assert "0" in captured.out
    assert "2" in captured.out

    # test group 4
    args = get_args([*base_args, "--analysis-groups", "4"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "LTC/BTC" in captured.out
    assert "ETH/BTC" in captured.out
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "exit_signal" in captured.out
    assert "roi" in captured.out
    assert "stop_loss" in captured.out
    assert "trailing_stop_loss" in captured.out
    assert "total_profit_pct" in captured.out
    assert "-10" in captured.out
    assert "-5" in captured.out
    assert "-4" in captured.out
    assert "0.5" in captured.out
    assert "1" in captured.out
    assert "2.5" in captured.out

    # test group 5
    args = get_args([*base_args, "--analysis-groups", "5"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "exit_signal" in captured.out
    assert "roi" in captured.out
    assert "stop_loss" in captured.out
    assert "trailing_stop_loss" in captured.out

    # test date filtering
    args = get_args(
        [*base_args, "--analysis-groups", "0", "1", "2", "--timerange", "20180129-20180130"]
    )
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" not in captured.out

    # Due to the backtest mock, there's no rejected signals generated.
    args = get_args([*base_args, "--rejected-signals"])
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "no rejected signals" in captured.out


def test_backtest_analysis_with_invalid_config(
    default_conf, mocker, caplog, testdatadir, user_dir, capsys
):
    caplog.set_level(logging.INFO)
    (user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)

    default_conf.update(
        {
            "use_exit_signal": True,
            "exit_profit_only": False,
            "exit_profit_offset": 0.0,
            "ignore_roi_if_entry_signal": False,
        }
    )
    patch_exchange(mocker)
    result1 = pd.DataFrame(
        {
            "pair": ["ETH/BTC", "LTC/BTC", "ETH/BTC", "LTC/BTC"],
            "profit_ratio": [0.025, 0.05, -0.1, -0.05],
            "profit_abs": [0.5, 2.0, -4.0, -2.0],
            "open_date": pd.to_datetime(
                [
                    "2018-01-29 18:40:00",
                    "2018-01-30 03:30:00",
                    "2018-01-30 08:10:00",
                    "2018-01-31 13:30:00",
                ],
                utc=True,
            ),
            "close_date": pd.to_datetime(
                [
                    "2018-01-29 20:45:00",
                    "2018-01-30 05:35:00",
                    "2018-01-30 09:10:00",
                    "2018-01-31 15:00:00",
                ],
                utc=True,
            ),
            "trade_duration": [235, 40, 60, 90],
            "is_open": [False, False, False, False],
            "stake_amount": [0.01, 0.01, 0.01, 0.01],
            "open_rate": [0.104445, 0.10302485, 0.10302485, 0.10302485],
            "close_rate": [0.104969, 0.103541, 0.102041, 0.102541],
            "is_short": [False, False, False, False],
            "enter_tag": [
                "enter_tag_long_a",
                "enter_tag_long_b",
                "enter_tag_long_a",
                "enter_tag_long_b",
            ],
            "exit_reason": [
                ExitType.ROI.value,
                ExitType.EXIT_SIGNAL.value,
                ExitType.STOP_LOSS.value,
                ExitType.TRAILING_STOP_LOSS.value,
            ],
        }
    )

    backtestmock = MagicMock(
        side_effect=[
            {
                "results": result1,
                "config": default_conf,
                "locks": [],
                "rejected_signals": 20,
                "timedout_entry_orders": 0,
                "timedout_exit_orders": 0,
                "canceled_trade_entries": 0,
                "canceled_entry_orders": 0,
                "replaced_entry_orders": 0,
                "final_balance": 1000,
            }
        ]
    )
    mocker.patch(
        "freqtrade.plugins.pairlistmanager.PairListManager.whitelist",
        PropertyMock(return_value=["ETH/BTC", "LTC/BTC", "DASH/BTC"]),
    )
    mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock)

    patched_configuration_load_config_file(mocker, default_conf)

    args = [
        "backtesting",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
        "--timeframe",
        "5m",
        "--timerange",
        "1515560100-1517287800",
        "--export",
        "signals",
        "--cache",
        "none",
    ]
    args = get_args(args)
    start_backtesting(args)

    captured = capsys.readouterr()
    assert "BACKTESTING REPORT" in captured.out
    assert "EXIT REASON STATS" in captured.out
    assert "LEFT OPEN TRADES REPORT" in captured.out

    base_args = [
        "backtesting-analysis",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
    ]

    # test with both entry and exit only arguments
    args = get_args(
        [
            *base_args,
            "--analysis-groups",
            "0",
            "--indicator-list",
            "close",
            "rsi",
            "profit_abs",
            "--entry-only",
            "--exit-only",
        ]
    )
    with pytest.raises(
        OperationalException,
        match=r"Cannot use --entry-only and --exit-only at the same time. Please choose one.",
    ):
        start_analysis_entries_exits(args)


def test_backtest_analysis_on_entry_and_rejected_signals_only_entry_signals(
    default_conf, mocker, caplog, testdatadir, user_dir, capsys
):
    caplog.set_level(logging.INFO)
    (user_dir / "backtest_results").mkdir(parents=True, exist_ok=True)

    default_conf.update(
        {
            "use_exit_signal": True,
            "exit_profit_only": False,
            "exit_profit_offset": 0.0,
            "ignore_roi_if_entry_signal": False,
        }
    )
    patch_exchange(mocker)
    result1 = pd.DataFrame(
        {
            "pair": ["ETH/BTC", "LTC/BTC", "ETH/BTC", "LTC/BTC"],
            "profit_ratio": [0.025, 0.05, -0.1, -0.05],
            "profit_abs": [0.5, 2.0, -4.0, -2.0],
            "open_date": pd.to_datetime(
                [
                    "2018-01-29 18:40:00",
                    "2018-01-30 03:30:00",
                    "2018-01-30 08:10:00",
                    "2018-01-31 13:30:00",
                ],
                utc=True,
            ),
            "close_date": pd.to_datetime(
                [
                    "2018-01-29 20:45:00",
                    "2018-01-30 05:35:00",
                    "2018-01-30 09:10:00",
                    "2018-01-31 15:00:00",
                ],
                utc=True,
            ),
            "trade_duration": [235, 40, 60, 90],
            "is_open": [False, False, False, False],
            "stake_amount": [0.01, 0.01, 0.01, 0.01],
            "open_rate": [0.104445, 0.10302485, 0.10302485, 0.10302485],
            "close_rate": [0.104969, 0.103541, 0.102041, 0.102541],
            "is_short": [False, False, False, False],
            "enter_tag": [
                "enter_tag_long_a",
                "enter_tag_long_b",
                "enter_tag_long_a",
                "enter_tag_long_b",
            ],
            "exit_reason": [
                ExitType.ROI.value,
                ExitType.EXIT_SIGNAL.value,
                ExitType.STOP_LOSS.value,
                ExitType.TRAILING_STOP_LOSS.value,
            ],
        }
    )

    backtestmock = MagicMock(
        side_effect=[
            {
                "results": result1,
                "config": default_conf,
                "locks": [],
                "rejected_signals": 20,
                "timedout_entry_orders": 0,
                "timedout_exit_orders": 0,
                "canceled_trade_entries": 0,
                "canceled_entry_orders": 0,
                "replaced_entry_orders": 0,
                "final_balance": 1000,
            }
        ]
    )
    mocker.patch(
        "freqtrade.plugins.pairlistmanager.PairListManager.whitelist",
        PropertyMock(return_value=["ETH/BTC", "LTC/BTC", "DASH/BTC"]),
    )
    mocker.patch("freqtrade.optimize.backtesting.Backtesting.backtest", backtestmock)

    patched_configuration_load_config_file(mocker, default_conf)

    args = [
        "backtesting",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
        "--timeframe",
        "5m",
        "--timerange",
        "1515560100-1517287800",
        "--export",
        "signals",
        "--cache",
        "none",
    ]
    args = get_args(args)
    start_backtesting(args)

    captured = capsys.readouterr()
    assert "BACKTESTING REPORT" in captured.out
    assert "EXIT REASON STATS" in captured.out
    assert "LEFT OPEN TRADES REPORT" in captured.out

    base_args = [
        "backtesting-analysis",
        "--config",
        "config.json",
        "--datadir",
        str(testdatadir),
        "--user-data-dir",
        str(user_dir),
    ]

    # test group 0 and indicator list
    args = get_args(
        [
            *base_args,
            "--analysis-groups",
            "0",
            "--indicator-list",
            "close",
            "rsi",
            "profit_abs",
            "--entry-only",
        ]
    )
    start_analysis_entries_exits(args)
    captured = capsys.readouterr()
    assert "LTC/BTC" in captured.out
    assert "ETH/BTC" in captured.out
    assert "enter_tag_long_a" in captured.out
    assert "enter_tag_long_b" in captured.out
    assert "exit_signal" in captured.out
    assert "roi" in captured.out
    assert "stop_loss" in captured.out
    assert "trailing_stop_loss" in captured.out
    assert "0.5" in captured.out
    assert "-4" in captured.out
    assert "-2" in captured.out
    assert "-3.5" in captured.out
    assert "50" in captured.out
    assert "0" in captured.out
    assert "0.016" in captured.out
    assert "34.049" in captured.out
    assert "0.104" in captured.out
    assert "52.829" in captured.out
    # assert indicator list
    assert "close" in captured.out
    assert "close (entry)" not in captured.out
    assert "0.016" in captured.out
    assert "rsi (entry)" not in captured.out
    assert "rsi" in captured.out
    assert "54.320" in captured.out
    assert "close (exit)" not in captured.out
    assert "rsi (exit)" not in captured.out
    assert "52.829" in captured.out
    assert "profit_abs" in captured.out