import json
import logging
import requests
import threading
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass, field
from datetime import datetime
from time import sleep

import numpy as np
import pandas as pd
from binance.client import Client
from ta import trend, momentum, volatility
from ta.utils import dropna

# ==========================
# Конфигурация и Логирование
# ==========================

@dataclass
class IndicatorParameters:
    rsi_window: int = 14
    macd_fast: int = 12
    macd_slow: int = 26
    macd_signal: int = 9
    bollinger_window: int = 20
    bollinger_dev: float = 2
    atr_window: int = 14

@dataclass
class SymbolFilter:
    min_volume: float = 1000
    exchange: str = "BINANCE"

@dataclass
class Config:
    binance_api_key: str
    binance_api_secret: str
    telegram_bot_token: str
    telegram_chat_id: str
    log_file: str
    symbol_filter: SymbolFilter
    analysis_interval: str
    indicator_parameters: IndicatorParameters = field(default_factory=IndicatorParameters)

def load_config(config_path='config.json') -> Config:
    """
    Загружает конфигурацию из файла JSON и преобразует её в объект Config.
    """
    try:
        with open(config_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
        symbol_filter = SymbolFilter(**data.get("symbol_filter", {}))
        indicator_params = IndicatorParameters(**data.get("indicator_parameters", {}))
        config = Config(
            binance_api_key=data.get("binance_api_key", ""),
            binance_api_secret=data.get("binance_api_secret", ""),
            telegram_bot_token=data.get("telegram_bot_token", ""),
            telegram_chat_id=data.get("telegram_chat_id", ""),
            log_file=data.get("log_file", "script.log"),
            symbol_filter=symbol_filter,
            analysis_interval=data.get("analysis_interval", "5m"),
            indicator_parameters=indicator_params
        )
        return config
    except Exception as e:
        logging.error(f"Ошибка загрузки конфигурации: {e}")
        raise

def setup_logging(log_file):
    """
    Настраивает логирование: информация и ошибки записываются в файл и выводятся в консоль.
    """
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s [%(levelname)s] %(message)s',
        handlers=[
            logging.FileHandler(log_file),
            logging.StreamHandler()
        ]
    )
    logging.info("Логирование настроено.")

# ==================================
# Инициализация Клиентов Binance и Telegram
# ==================================

def init_binance_client(config: Config) -> Client:
    """
    Инициализирует клиента Binance с использованием API-ключей.
    """
    try:
        client = Client(config.binance_api_key, config.binance_api_secret)
        logging.info("Binance клиент успешно инициализирован.")
        return client
    except Exception as e:
        logging.error(f"Ошибка инициализации Binance клиента: {e}")
        raise

def send_telegram_message(config: Config, message: str):
    """
    Отправляет сообщение в Telegram с использованием бота.
    """
    try:
        url = f"https://api.telegram.org/bot{config.telegram_bot_token}/sendMessage"
        payload = {
            "chat_id": config.telegram_chat_id,
            "text": message,
            "parse_mode": "Markdown",
            "disable_web_page_preview": True
        }
        response = requests.post(url, data=payload, timeout=10)
        if response.status_code != 200:
            logging.error(f"Ошибка отправки сообщения в Telegram: {response.text}")
        else:
            logging.info("Сообщение успешно отправлено в Telegram.")
    except Exception as e:
        logging.error(f"Ошибка при отправке сообщения в Telegram: {e}")

# ====================================
# Получение и Обработка Данных с Binance
# ====================================

def get_all_futures_symbols(client: Client, config: Config):
    """
    Получает список всех фьючерсных символов с Binance.
    """
    try:
        exchange_info = client.futures_exchange_info()
        symbols = [s['symbol'] for s in exchange_info['symbols'] if s['contractType'] == 'PERPETUAL']
        logging.info(f"Получено {len(symbols)} фьючерсных символов.")
        return symbols
    except Exception as e:
        logging.error(f"Ошибка получения фьючерсных символов: {e}")
        return []

def get_historical_klines(client: Client, symbol: str, interval: str, limit: int = 500) -> pd.DataFrame:
    """
    Получает исторические данные свечей для указанного символа и интервала.
    """
    try:
        klines = client.futures_klines(symbol=symbol, interval=interval, limit=limit)
        df = pd.DataFrame(klines, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 'volume',
            'close_time', 'quote_asset_volume', 'number_of_trades',
            'taker_buy_base_asset_volume', 'taker_buy_quote_asset_volume', 'ignore'
        ])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        for col in ['open', 'high', 'low', 'close', 'volume']:
            df[col] = pd.to_numeric(df[col])
        df.set_index('timestamp', inplace=True)
        logging.debug(f"Получены исторические данные для {symbol}.")
        return df
    except Exception as e:
        logging.error(f"Ошибка получения данных для {symbol}: {e}")
        return pd.DataFrame()

def calculate_indicators(df: pd.DataFrame, config: Config) -> pd.DataFrame:
    """
    Рассчитывает технические индикаторы на основе исторических данных.
    """
    try:
        df = dropna(df)
        # RSI
        rsi_window = config.indicator_parameters.rsi_window
        df['rsi'] = momentum.RSIIndicator(close=df['close'], window=rsi_window).rsi()
        # MACD
        macd_fast = config.indicator_parameters.macd_fast
        macd_slow = config.indicator_parameters.macd_slow
        macd_signal = config.indicator_parameters.macd_signal
        macd = trend.MACD(close=df['close'], window_fast=macd_fast, window_slow=macd_slow, window_sign=macd_signal)
        df['macd'] = macd.macd()
        df['macd_signal'] = macd.macd_signal()
        df['macd_diff'] = macd.macd_diff()
        # Bollinger Bands
        bollinger_window = config.indicator_parameters.bollinger_window
        bollinger_dev = config.indicator_parameters.bollinger_dev
        bollinger = volatility.BollingerBands(close=df['close'], window=bollinger_window, window_dev=bollinger_dev)
        df['bollinger_hband'] = bollinger.bollinger_hband()
        df['bollinger_lband'] = bollinger.bollinger_lband()
        df['bollinger_middle'] = bollinger.bollinger_mavg()
        # ATR
        atr_window = config.indicator_parameters.atr_window
        df['atr'] = volatility.AverageTrueRange(high=df['high'], low=df['low'], close=df['close'], window=atr_window).average_true_range()
        logging.debug("Технические индикаторы рассчитаны.")
        return df
    except Exception as e:
        logging.error(f"Ошибка расчета индикаторов: {e}")
        return df

def determine_fibonacci_levels(df: pd.DataFrame) -> dict:
    """
    Определяет уровни Фибоначчи на основе последних 50 свечей.
    """
    try:
        recent = df.tail(50)
        high = recent['high'].max()
        low = recent['low'].min()

        # Основные уровни Фибоначчи
        fib_levels = {
            'fib_0.236': high - 0.236 * (high - low),
            'fib_0.382': high - 0.382 * (high - low),
            'fib_0.500': high - 0.500 * (high - low),
            'fib_0.618': high - 0.618 * (high - low),
            'fib_0.764': high - 0.764 * (high - low)
        }

        logging.debug(f"Уровни Фибоначчи: {fib_levels}")
        return fib_levels
    except Exception as e:
        logging.error(f"Ошибка определения уровней Фибоначчи: {e}")
        return {}

def determine_support_resistance_combined(df: pd.DataFrame) -> dict:
    """
    Определяет уровни поддержки и сопротивления на основе Pivot Points и уровней Фибоначчи.
    """
    try:
        # Расчет Pivot Points
        recent_pivot = df.tail(50)
        high_p = recent_pivot['high'].max()
        low_p = recent_pivot['low'].min()
        close_p = recent_pivot['close'].iloc[-1]

        PP = (high_p + low_p + close_p) / 3
        R1 = 2 * PP - low_p
        S1 = 2 * PP - high_p

        pivot_levels = {
            'pivot': PP,
            'support_pivot': S1,
            'resistance_pivot': R1
        }

        # Расчет уровней Фибоначчи
        fib_levels = determine_fibonacci_levels(df)

        # Комбинирование уровней: выбираем уровни, которые наиболее близки друг к другу
        combined_levels = {
            'pivot': PP,
            'support': S1,  # Можно расширить логику для выбора наиболее релевантных уровней
            'resistance': R1
        }

        # Например, если уровень Фибоначчи близок к S1 или R1, можно использовать его
        for key, level in fib_levels.items():
            if abs(level - S1) / S1 < 0.02:  # Если уровень Фибоначчи близок к S1 (разница <2%)
                combined_levels['support'] = level
                logging.debug(f"Фибоначчи уровень {key} используется для поддержки: {level}")
            if abs(level - R1) / R1 < 0.02:
                combined_levels['resistance'] = level
                logging.debug(f"Фибоначчи уровень {key} используется для сопротивления: {level}")

        logging.debug(f"Комбинированные уровни поддержки и сопротивления: {combined_levels}")
        return combined_levels
    except Exception as e:
        logging.error(f"Ошибка определения комбинированных уровней поддержки и сопротивления: {e}")
        return {}

def analyze_signal(df: pd.DataFrame, levels: dict) -> dict:
    """
    Анализирует последний сигнал на основе индикаторов и уровней поддержки/сопротивления.
    """
    try:
        latest = df.iloc[-1]
        signal = None
        confidence = 0

        # Условия для BUY сигнала
        if (latest['close'] < levels['support'] and
            latest['rsi'] < 30 and  # Ослабленный порог RSI
            latest['macd'] > latest['macd_signal']):
            signal = 'BUY'
            confidence = 85  # Ослабленный порог достоверности

        # Условия для SELL сигнала
        elif (latest['close'] > levels['resistance'] and
              latest['rsi'] > 70 and  # Ослабленный порог RSI
              latest['macd'] < latest['macd_signal']):
            signal = 'SELL'
            confidence = 80  # Ослабленный порог достоверности

        logging.debug(f"Сигнал: {signal}, Достоверность: {confidence}")
        return {
            'signal': signal,
            'confidence': confidence
        }
    except Exception as e:
        logging.error(f"Ошибка анализа сигнала: {e}")
        return {'signal': None, 'confidence': 0}

# ===========================
# Обработка и Анализ Символов
# ===========================

def plot_indicators(df: pd.DataFrame, symbol: str, levels: dict):
    """
    Визуализирует цену и уровни поддержки/сопротивления.
    """
    import matplotlib.pyplot as plt

    plt.figure(figsize=(14, 7))
    plt.plot(df.index, df['close'], label='Цена закрытия', color='blue')
    plt.axhline(y=levels['pivot'], color='gray', linestyle='--', label='Pivot Point')
    plt.axhline(y=levels['support'], color='green', linestyle='-', label='Поддержка (S1)')
    plt.axhline(y=levels['resistance'], color='red', linestyle='-', label='Сопротивление (R1)')

    plt.title(f'{symbol} - Pivot Points')
    plt.xlabel('Время')
    plt.ylabel('Цена')
    plt.legend()
    plt.show()

def process_symbol(client: Client, config: Config, symbol: str) -> dict:
    """
    Обрабатывает один символ: получает данные, рассчитывает индикаторы, анализирует сигнал.
    Возвращает информацию о сигнале, если он соответствует условиям.
    """
    try:
        logging.info(f"Начата обработка символа: {symbol}")
        df = get_historical_klines(client, symbol, config.analysis_interval)
        if df.empty:
            logging.warning(f"Нет данных для символа: {symbol}")
            return {}
        df = calculate_indicators(df, config)
        if df.empty:
            logging.warning(f"Нет данных после расчёта индикаторов для символа: {symbol}")
            return {}
        levels = determine_support_resistance_combined(df)
        if not levels:
            logging.warning(f"Не удалось определить уровни для символа: {symbol}")
            return {}
        analysis = analyze_signal(df, levels)

        # Логирование значений индикаторов
        latest = df.iloc[-1]
        logging.debug(f"Индикаторы для {symbol}: RSI={latest['rsi']}, MACD={latest['macd']}, "
                      f"MACD_Signal={latest['macd_signal']}, Close={latest['close']}, "
                      f"Support={levels.get('support', 'N/A')}, Resistance={levels.get('resistance', 'N/A')}")

        if analysis['signal'] and analysis['confidence'] >= 80:  # Снижение порога достоверности
            volume = df['volume'].iloc[-1]
            if volume >= config.symbol_filter.min_volume:
                result = {
                    'symbol': symbol,
                    'signal': analysis['signal'],
                    'confidence': analysis['confidence'],
                    'pivot': round(levels['pivot'], 2),
                    'support': round(levels['support'], 2),
                    'resistance': round(levels['resistance'], 2),
                    'volume': volume,
                    'close': round(df['close'].iloc[-1], 2)
                }
                logging.info(f"Сигнал найден: {result}")

                # Опционально: Визуализация индикаторов для сигнальных монет
                # plot_indicators(df, symbol, levels)

                return result
            else:
                logging.debug(f"Символ {symbol} отклонен из-за объема: {volume} < {config.symbol_filter.min_volume}")
        else:
            logging.debug(f"Символ {symbol} не имеет сигнала или низкой достоверности: {analysis['signal']} с достоверностью {analysis['confidence']}%")

        return {}
    except Exception as e:
        logging.error(f"Ошибка обработки символа {symbol}: {e}")
        return {}

# ==========================
# Главная Функция Скрипта
# ==========================

def main():
    """
    Главная функция, которая загружает конфигурацию, инициализирует клиентов,
    получает список символов, обрабатывает их и отправляет отчёт в Telegram.
    """
    try:
        # Загрузка конфигурации
        config = load_config()
        # Настройка логирования
        setup_logging(config.log_file)
        logging.info("Скрипт запущен.")

        # Инициализация клиентов
        client = init_binance_client(config)

        # Получение всех фьючерсных символов
        symbols = get_all_futures_symbols(client, config)
        if not symbols:
            logging.error("Не удалось получить список символов. Завершение работы скрипта.")
            return

        results = []

        # Параллельная обработка символов
        with ThreadPoolExecutor(max_workers=10) as executor:
            futures = [executor.submit(process_symbol, client, config, symbol) for symbol in symbols]
            for future in futures:
                result = future.result()
                if result:
                    results.append(result)

        # Формирование и отправка отчёта в Telegram
        if results:
            report = "📈 *Перспективные монеты для внутридневной торговли (M5)* 📉\n\n"
            for res in results:
                report += f"**{res['symbol']}**\n"
                report += f"Сигнал: `{res['signal']}` (Достоверность: {res['confidence']}%)\n"
                report += f"Цена закрытия: {res['close']}$\n"
                report += f"Pivot Point: {res['pivot']}$\n"
                report += f"Поддержка (S1): {res['support']}$, Сопротивление (R1): {res['resistance']}$\n"
                report += f"Объем: {res['volume']}\n\n"
            send_telegram_message(config, report)
            logging.info("Отчёт отправлен в Telegram.")
        else:
            send_telegram_message(config, "🔍 *Нет подходящих сигналов на данный момент.*")
            logging.info("Нет подходящих сигналов для отправки.")

        logging.info("Скрипт завершил работу.")
    except Exception as e:
        logging.critical(f"Критическая ошибка: {e}")

if __name__ == "__main__":
    main()     при каких праметрах будут отобраны монеты?