# source: https://raw.githubusercontent.com/ExaByt3s/freeqtrade_gradient_repo/f83f1bd76d6799b5de94208b033a0ff632438f17/freqtrade/strategy_zigzag.py
import copy
import json
import logging
import math
import os
import sys
import time
from datetime import datetime
from functools import reduce
from typing import Literal, Optional, Union

import numpy
import pandas as pd
import talib.abstract as ta
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, merge_informative_pair
from pandas import DataFrame
from technical import qtpylib

import indicator

# log = logging.getLogger(__name__)
# log.setLevel(logging.DEBUG)
log = logging.getLogger(__name__)
log_level: Literal[0, 1, 2] = 2

if log_level == 2:
    log.setLevel(logging.DEBUG)
elif log_level == 1:
    log.setLevel(logging.INFO)
elif log_level == 0:
    log.setLevel(logging.ERROR)

# See https://stackoverflow.com/questions/46807204/python-logging-duplicated
# See https://stackoverflow.com/questions/14058453/making-python-loggers-output-all-messages-to-stdout-in-addition-to-log-file
if not log.handlers:
    sh = logging.StreamHandler(sys.stderr)
    sh.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(funcName)s() %(message)s'))
    log.addHandler(sh)

log.propagate = False

def json_dumps(object_: dict) -> str:
    return json.dumps(object_, indent=4, default=list, sort_keys=True)

class github_ExaByt3s_freeqtrade_gradient_repo__strategy_zigzag__20230114_173148(IStrategy):
    def __init__(self, config: dict) -> None:
        super().__init__(config=config)
        self.path_runtime = './runtime.json'
        self.indent = 4

    def runtime_write(self) -> None:
        with open(self.path_runtime, mode='w') as file:
            json.dump(self.runtime, file, indent=self.indent, sort_keys=True)

    def runtime_load(self) -> None:
        with open(self.path_runtime, mode='r') as file:
            self.runtime = json.load(file)

    def runtime_pair_initial(self) -> dict:
        threshold = 0.04

        return {
            'previous': {
                'threshold': None,
                'stoploss': None,
                'takeprofit': None,
                'direction': None,
                'profit': None,
            },
            'current': {
                'threshold': threshold,
                'stoploss': -threshold,
                'takeprofit': threshold,
                'direction': None,
                'profit': 0.,
            },
        }

    def runtime_pair_reset(self, pair: str) -> None:
        if pair not in self.runtime:
            raise Exception

        self.runtime[pair] = self.runtime_pair_initial()

    def runtime_update(self, list_pair: list) -> None:  # list[str]
        runtime_old = self.runtime
        self.runtime = {}

        for pair in runtime_old:
            if runtime_old[pair] != self.runtime_pair_initial():
                self.runtime[pair] = runtime_old[pair]

        for pair in list_pair:
            if pair not in self.runtime:
                self.runtime[pair] = self.runtime_pair_initial()

    def bot_start(self, **kwargs) -> None:
        time_begin = time.perf_counter()

        if not self.dp:
            raise Exception('DataProvider is required')

        if self.dp.runmode.value == 'hyperopt':
            raise Exception('Hyperopt is not supported')

        if self.dp.runmode.value in ['dry_run', 'live']:
            if os.path.isfile(self.path_runtime):
                self.runtime_load()
            else:
                self.runtime = {}

        elif self.dp.runmode.value == 'backtest':
            self.runtime = {}

        if self.dp.runmode.value != 'plot':
            list_pair = self.dp.current_whitelist()
            self.runtime_update(list_pair)
        # log.debug(f'runtime:\n{json_dumps(self.runtime)}')

        time_end = time.perf_counter()
        log.info(
            f'runmode:{self.dp.runmode.value}'
            f' timeframe:{self.timeframe}'
            f' {time_end - time_begin:0.4f} (second)'
        )

    def bot_loop_start(self, **kwargs) -> None:
        time_begin = time.perf_counter()

        if self.dp.runmode.value in ['dry_run', 'live']:
            self.runtime_write()

        time_end = time.perf_counter()
        log.info(
            f'runmode:{self.dp.runmode.value}'
            f' timeframe:{self.timeframe}'
            f' {time_end - time_begin:0.4f} (second)'
        )

    # Disable ROI
    # minimal_roi: dict[str, int] = {
    minimal_roi: dict = {
        '0': 10000  # 10000 * 100%
    }

    # Disable stoploss
    stoploss: float = -1.00  # -100%

    plot_config = {
        "main_plot": {},
        "subplots": {},
    }

    startup_candle_count: int = 20

    def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        a = numpy.arange(len(dataframe)) % 2
        dataframe.loc[a == 1, ['enter_long', 'enter_tag']] = (1, 'Long')
        dataframe.loc[a == 0, ['enter_short', 'enter_tag']] = (1, 'Short')

        return dataframe

    def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float,
                            min_stake: Optional[float], max_stake: float, leverage: float, entry_tag: Optional[str], side: str,
                            **kwargs) -> float:

        previous_direction = self.runtime[pair]['previous']['direction']
        if previous_direction == side:
            return 0.

        if min_stake > max_stake:
            log.info(
                f'Entry signal has skipped: min_stake > max_stake: {min_stake:0.4f} > {max_stake:0.4f}'
            )
            return 0.

        initial_stake = self.stake_amount

        if initial_stake < min_stake:
            initial_stake = math.ceil(min_stake)

        if initial_stake > max_stake:
            log.info(
                f'Entry signal has skipped: initial_stake > max_stake: {initial_stake:0.4f} > {max_stake:0.4f}'
            )
            return 0.

        threshold_step = 0.0025
        # threshold = abs(self.runtime[pair]['current']['stoploss']) + self.runtime[pair]['current']['takeprofit'] / 4
        # threshold = (self.runtime[pair]['current']['takeprofit'] - self.runtime[pair]['current']['stoploss']) / 4

        if self.runtime[pair]['previous']['profit'] is None:
            pass

        # elif self.runtime[pair]['previous']['profit'] < 0:
        elif self.runtime[pair]['previous']['profit'] < 0 and self.runtime[pair]['previous']['stoploss'] < -0.02:
            # self.runtime[pair]['current']['threshold'] += threshold_step
            # self.runtime[pair]['current']['threshold'] = threshold
            self.runtime[pair]['current']['stoploss'] = self.runtime[pair]['previous']['stoploss'] + threshold_step

        # elif self.runtime[pair]['previous']['profit'] > 0:
        elif self.runtime[pair]['previous']['profit'] < 0 and self.runtime[pair]['previous']['stoploss'] > -0.02:
            # self.runtime[pair]['current']['threshold'] -= threshold_step
            # self.runtime[pair]['current']['threshold'] = threshold
            self.runtime[pair]['current']['stoploss'] = self.runtime[pair]['previous']['stoploss'] - threshold_step

        # self.runtime[pair]['current']['threshold'] = max(0.01, self.runtime[pair]['current']['threshold'])
        # self.runtime[pair]['current']['stoploss'] = -self.runtime[pair]['current']['threshold']
        self.runtime[pair]['current']['threshold'] = 0.04
        self.runtime[pair]['current']['stoploss'] = min(-0.01, self.runtime[pair]['current']['stoploss'])

        self.runtime[pair]['current']['takeprofit'] = self.runtime[pair]['current']['threshold']
        self.runtime[pair]['current']['direction'] = side
        self.runtime[pair]['current']['profit'] = 0.

        log.debug(f'runtime[pair]:\n{json_dumps(self.runtime[pair])}')

        log.info(
            f'current_time:{current_time}'
            f' pair:{pair}'
            f' (initial_stake/stake_amount):({initial_stake}/{self.stake_amount})'
            f' side:{side}'
            f' entry_tag:{entry_tag}'
            f' min_stake:{min_stake:0.4f}'
            f' max_stake:{max_stake:0.4f}'
            f' current_rate:{current_rate:0.4f}'
        )

        return initial_stake

    def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float,
                    **kwargs) -> Optional[Union[str, bool]]:

        # dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
        # candle_last = dataframe.iloc[-1].squeeze()
        threshold = self.runtime[pair]['current']['threshold']

        # takeprofit_candidate = current_profit
        takeprofit_candidate = threshold + current_profit
        stoploss_candidate = -threshold + current_profit

        if takeprofit_candidate > self.runtime[pair]['current']['takeprofit']:
            self.runtime[pair]['current']['takeprofit'] = takeprofit_candidate

        if stoploss_candidate > self.runtime[pair]['current']['stoploss']:
            self.runtime[pair]['current']['stoploss'] = stoploss_candidate

        reason = None

        # if current_profit > threshold and current_profit < self.runtime[pair]['takeprofit'] / 4 * 3:
            # reason = f'takeprofit_{self.runtime[pair]["takeprofit"]:0.2f}'
        # if current_profit < 0 and current_profit < self.runtime[pair]['stoploss']:
            # reason = f'stoploss_{self.runtime[pair]["stoploss"]:0.2f}'

        if current_profit > self.runtime[pair]['current']['takeprofit']:
            reason = f'takeprofit_{self.runtime[pair]["current"]["takeprofit"]:0.2f}'

        if current_profit < self.runtime[pair]['current']['stoploss']:
            reason = f'stoploss_{self.runtime[pair]["current"]["stoploss"]:0.2f}'

        if reason is None:
            return False

        color_ansi: dict = {  # dict[str, str]
            'black'  : '\x1b[0;30m',
            'blue'   : '\x1b[0;34m',
            'cyan'   : '\x1b[0;36m',
            'green'  : '\x1b[0;32m',
            'magenda': '\x1b[0;35m',
            'red'    : '\x1b[0;31m',
            'yellow' : '\x1b[0;33m',
            'reset'  : '\x1b[0m'   ,
        }

        color_begin = ''
        color_end = color_ansi['reset']

        if current_profit > 0:
            color_begin = color_ansi['green']
        elif current_profit < 0:
            color_begin = color_ansi['red']

        self.runtime[pair]['current']['profit'] = current_profit
        self.runtime[pair]['previous'] = copy.deepcopy(self.runtime[pair]['current'])
        log.debug(f'runtime:\n{json_dumps(self.runtime)}')

        log.info(
            f'{color_begin}'
            f'current_time:{current_time}'
            f' pair:{pair}'
            f' reason:{reason}'
            f' current_profit:{current_profit}'
            f' open_rate:{trade.open_rate:0.4f}'
            f' current_rate:{current_rate:0.4f}'
            f' timedelta:{current_time - trade.open_date_utc}'
            f'{color_end}'
        )

        return reason

    def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe

    def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
        return dataframe
