from curses import panel
import json
from tkinter import BOTTOM
import ccxt
import pandas as pd
import time
import requests
from datetime import datetime
from rich.console import Console
from rich.table import Table
from rich.live import Live
from rich.theme import Theme
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()
API_KEY = os.getenv("BINANCE_API_KEY")
SECRET_KEY = os.getenv("BINANCE_SECRET_KEY")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")

# Initialize Rich console with custom theme
custom_theme = Theme({
    "success": "bold green",
    "error": "bold red",
    "warning": "bold yellow",
    "info": "bold cyan",
    "header": "bold magenta"
})
console = Console(theme=custom_theme);

console.print(f"[info]API Key loaded: {bool(API_KEY)}[/info]")
console.print(f"[info]Secret Key loaded: {bool(SECRET_KEY)}[/info]")

class TradingStrategy:
    def __init__(self, params):
        self.fast_ema = params['fast_ema']
        self.slow_ema = params['slow_ema']
        self.rsi_period = params['rsi_period']
        self.macd_fast_period = params['macd_fast_period']
        self.macd_slow_period = params['macd_slow_period']
        self.macd_signal_period = params['macd_signal_period']
        self.stop_loss_pct = params['stop_loss_pct']
        self.risk_reward_ratio = params['risk_reward_ratio']
        self.breakeven_profit_pct = params['breakeven_profit_pct']
        self.trailing_stop_pct = params['trailing_stop_pct']
        self.partial_tp_levels = params['partial_tp_levels']
        

    def rsi(self, series):
        delta = series.diff()
        gain = delta.where(delta > 0, 0.0)
        loss = -delta.where(delta < 0, 0.0)
        avg_gain = gain.rolling(self.rsi_period).mean()
        avg_loss = loss.rolling(self.rsi_period).mean()
        rs = avg_gain / (avg_loss + 1e-8)
        return 100 - (100 / (1 + rs))

    def _detect_simple_divergence(self, df, indicator_col, divergence_type):
        if len(df) < 2:
            return False
        last = df.iloc[-1]
        prev = df.iloc[-2]
        price_current = last['close']
        price_prev = prev['close']
        indicator_current = last[indicator_col]
        indicator_prev = prev[indicator_col]
        if divergence_type == 'bullish':
            return price_current < price_prev and indicator_current > indicator_prev
        elif divergence_type == 'bearish':
            return price_current > price_prev and indicator_current < indicator_prev
        return False

    def get_processed_dataframe(self, ohlcv_df):
        df = ohlcv_df.copy()
        price = df['close']
        df['EMA_FAST'] = price.ewm(span=self.fast_ema, adjust=False).mean()
        df['EMA_SLOW'] = price.ewm(span=self.slow_ema, adjust=False).mean()
        df['RSI'] = self.rsi(price)

        ema_fast_macd = price.ewm(span=self.macd_fast_period, adjust=False).mean()
        ema_slow_macd = price.ewm(span=self.macd_slow_period, adjust=False).mean()
        macd_line = ema_fast_macd - ema_slow_macd
        signal_line = macd_line.ewm(span=self.macd_signal_period, adjust=False).mean()
        df['MACD_LINE'] = macd_line
        df['SIGNAL_LINE'] = signal_line
        df['MACD_HIST'] = macd_line - signal_line
        return df

    def check_signal(self, df, df_confirmation, open_positions):
        if len(df) < 3:
            return None, None

        last = df.iloc[-1]
        prev = df.iloc[-2]
        current_price = last['close']

        

        # (Your original strategy continues here for 'full' mode)
        # Calculate MACD histogram confirmation
        macd_conf = df_confirmation.iloc[-1]['MACD_HIST'] if len(df_confirmation) > 0 else 0
        macd_bullish_div = self._detect_simple_divergence(df, 'MACD_HIST', 'bullish')
        macd_bearish_div = self._detect_simple_divergence(df, 'MACD_HIST', 'bearish')
        rsi_bullish_div = self._detect_simple_divergence(df, 'RSI', 'bullish')
        rsi_bearish_div = self._detect_simple_divergence(df, 'RSI', 'bearish')

        # Entry signals
        if (prev['EMA_FAST'] < prev['EMA_SLOW'] and last['EMA_FAST'] > last['EMA_SLOW']
            and last['RSI'] > 50 and last['MACD_HIST'] > 0
            and last['close'] > prev['close']
            and macd_conf > 0
            and (macd_bullish_div or rsi_bullish_div)):
            return 'buy', current_price
        elif (prev['EMA_FAST'] > prev['EMA_SLOW'] and last['EMA_FAST'] < last['EMA_SLOW']
              and last['RSI'] < 50 and last['MACD_HIST'] < 0
              and last['close'] < prev['close']
              and macd_conf < 0
              and (macd_bearish_div or rsi_bearish_div)):
            return 'sell', current_price
        return None, None

    def calculate_position_size(self, account_balance, current_price, risk_pct, risk_multiplier):
        risk_amount = (risk_pct / 100) * account_balance * risk_multiplier
        size = risk_amount / current_price
        # Minimum size check
        return max(0.25, size)

    def calculate_sl_tp(self, current_price, signal, stop_loss_pct, risk_reward_ratio):
        if signal == 'buy':
            sl_price = current_price * (1 - stop_loss_pct / 100)
            tp_price = current_price * (1 + stop_loss_pct * risk_reward_ratio / 100)
        else:
            sl_price = current_price * (1 + stop_loss_pct / 100)
            tp_price = current_price * (1 - stop_loss_pct * risk_reward_ratio / 100)
        return sl_price, tp_price

    def monitor_position_updates(self, position, current_price):
        # Update SL for breakeven and trailing stops
        if position['direction'] == 'buy':
            profit_pct = ((current_price - position['entry_price']) / position['entry_price']) * 100
            if profit_pct >= self.breakeven_profit_pct and position['sl'] < position['entry_price']:
                position['sl'] = position['entry_price']
            # Trailing stop
            new_sl = current_price * (1 - self.trailing_stop_pct / 100)
            if new_sl > position['sl']:
                position['sl'] = new_sl
        else:
            profit_pct = ((position['entry_price'] - current_price) / position['entry_price']) * 100
            if profit_pct >= self.breakeven_profit_pct and position['sl'] > position['entry_price']:
                position['sl'] = position['entry_price']
            # Trailing stop
            new_sl = current_price * (1 + self.trailing_stop_pct / 100)
            if new_sl < position['sl']:
                position['sl'] = new_sl
        # Mark partial TP levels hit
        for i, tp_level in enumerate(self.partial_tp_levels):
            if 'taken_profit_levels' not in position:
                position['taken_profit_levels'] = []
            if tp_level['profit_pct'] not in position.get('taken_profit_levels', []):
                if (position['direction'] == 'buy' and current_price >= position['entry_price'] * (1 + tp_level['profit_pct']/100)) \
                   or (position['direction'] == 'sell' and current_price <= position['entry_price'] * (1 - tp_level['profit_pct']/100)):
                    position['taken_profit_levels'].append(tp_level['profit_pct'])
        return position

class AutoTrader:
    def __init__(self, api_key, secret_key):
        self.exchange = ccxt.binance({
            'apiKey': api_key,
            'secret': secret_key,
            'options': {'defaultType': 'future'}
        })
        self.exchange.load_markets()
        self.symbol = 'XRPUSDT'
        self.timeframe = '5m'
        self.confirmation_timeframe = '15m'
        self.leverage = 2
        self.account_balance = 0
        self.open_positions = []
        self.position_history = []
        self.risk_pct = 1.0  # 1% risk per trade
        self.risk_multiplier = 10 # For position size calculation
        

        # Strategy parameters
        self.strategy_params = {
            'fast_ema': 10,
            'slow_ema': 21,
            'rsi_period': 14,
            'macd_fast_period': 12,
            'macd_slow_period': 26,
            'macd_signal_period': 9,
            'stop_loss_pct': 1.5,
            'risk_reward_ratio': 2.0,
            'breakeven_profit_pct': 1.0,
            'trailing_stop_pct': 0.5,
            'partial_tp_levels': [{'profit_pct': 1.0}]
        }
        self.strategy = TradingStrategy(self.strategy_params)
        self.last_signal_time = 0
        self.cooldown_seconds = 300  # 5 minutes
        self.telegram_token = TELEGRAM_TOKEN
        self.telegram_chat_id = TELEGRAM_CHAT_ID
        self.console = console
        self.console.print("[info]AutoTrader initialized.[/info]")

        self.load_positions() # Load positions at startup
        self.update_account_balance()
        self.console.print(f"[info]Account balance updated: {self.account_balance}[/info]")

    def save_positions(self):
        try:
            with open('trading_data.json', 'w') as f:
                json.dump(self.open_positions, f, indent=4)
            self.console.print("[info]Open positions saved to trading_data.json[/info]")
        except Exception as e:
            self.console.print(f"[red]Error saving positions: {e}[/red]")

    def load_positions(self):
        try:
            if os.path.exists('trading_data.json'):
                with open('trading_data.json', 'r') as f:
                    self.open_positions = json.load(f)
                self.console.print("[info]Open positions loaded from trading_data.json[/info]")
            else:
                self.console.print("[info]No existing trading_data.json found. Starting with no open positions.[/info]")
        except Exception as e:
            self.console.print(f"[red]Error loading positions: {e}. Starting with no open positions.[/red]")
            self.open_positions = [] # Ensure it's an empty list on error

    def update_account_balance(self):
        try:
            balance_info = self.exchange.fetch_balance()
            self.account_balance = balance_info['USDT']['free']
        except Exception as e:
            self.console.print(f"[red]Error fetching balance: {e}[/red]")

    def fetch_ohlcv(self, timeframe):
        self.console.print(f"[info]Fetching OHLCV for {self.symbol} {timeframe}...[/info]")
        ohlcv = self.exchange.fetch_ohlcv(self.symbol, timeframe, limit=100)
        self.console.print(f"[info]Fetched {len(ohlcv)} OHLCV candles for {self.symbol} {timeframe}.[/info]")
        df = pd.DataFrame(ohlcv, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume'])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        return df

    def calculate_position_size(self, current_price):
        # Use a fixed risk percentage, e.g., 1%
        return self.strategy.calculate_position_size(self.account_balance, current_price, self.risk_pct, self.risk_multiplier)

    def open_trade(self, signal, current_price, size):
        sl, tp = self.strategy.calculate_sl_tp(current_price, signal, self.strategy_params['stop_loss_pct'], self.strategy_params['risk_reward_ratio'])
        
        order = None
        try:
            if signal == 'buy':
                self.console.print(f"[info]Placing BUY market order for {size:.4f} {self.symbol} at {current_price:.4f}...[/info]")
                order = self.exchange.create_market_buy_order(self.symbol, size)
            else: # signal == 'sell'
                self.console.print(f"[info]Placing SELL market order for {size:.4f} {self.symbol} at {current_price:.4f}...[/info]")
                order = self.exchange.create_market_sell_order(self.symbol, size)
            
            # Update position details with actual executed order info
            executed_price = order['price'] if order['price'] else current_price # Use executed price if available
            executed_size = order['filled'] if order['filled'] else size # Use filled size if available

            position = {
                'symbol': self.symbol,
                'direction': signal,
                'entry_price': executed_price,
                'size': executed_size,
                'sl': sl,
                'tp': tp,
                'taken_profit_levels': []
            }
            self.open_positions.append(position)
            msg = f"Opened {signal.upper()} at {executed_price:.4f}. SL: {sl:.4f}, TP: {tp:.4f}. Executed Size: {executed_size:.4f}"
            self.console.print(msg, style="success")
            self.send_telegram_message(msg)
            self.last_signal_time = time.time()
            self.save_positions() # Save positions after opening a trade

        except ccxt.NetworkError as e:
            self.console.print(f"[red]Network error while placing order:[/red] {e}")
            self.send_telegram_message(f"Order failed (Network Error): {e}")
        except ccxt.ExchangeError as e:
            self.console.print(f"[red]Exchange error while placing order:[/red] {e}")
            self.send_telegram_message(f"Order failed (Exchange Error): {e}")
        except Exception as e:
            self.console.print(f"[red]An unexpected error occurred while placing order:[/red] {e}")
            self.send_telegram_message(f"Order failed (Unexpected Error): {e}")

    def close_position(self, position, exit_price, reason):
        pnl = 0
        if position['direction'] == 'buy':
            pnl = (exit_price - position['entry_price']) * position['size']
        else:
            pnl = (position['entry_price'] - exit_price) * position['size']
        self.account_balance += pnl
        self.position_history.append({'pnl': pnl, 'direction': position['direction'], 'entry': position['entry_price'], 'exit': exit_price})
        self.open_positions.remove(position)
        msg = f"Closed {position['direction'].upper()} at {exit_price:.4f} due to {reason}. PnL: ${pnl:.2f}"
        self.console.print(msg, style="success" if pnl >= 0 else "error")
        self.send_telegram_message(msg)
        self.save_positions() # Save positions after closing a trade

    def send_telegram_message(self, message):
        if self.telegram_token and self.telegram_chat_id:
            url = f"https://api.telegram.org/bot{self.telegram_token}/sendMessage"
            payload = {'chat_id': self.telegram_chat_id, 'text': message}
            try:
                requests.post(url, data=payload)
            except Exception as e:
                self.console.print(f"[red]Telegram error:[/red] {e}")

    def generate_dashboard(self, current_price):
        table = Table(title="Trading Dashboard")
        table.add_column("Direction", justify="left")
        table.add_column("Entry Price", justify="right")
        table.add_column("Current Price", justify="right")
        table.add_column("Unrealized PnL", justify="right")
        total_unrealized = 0.0
        for pos in self.open_positions:
            entry = pos['entry_price']
            size = pos['size']
            if pos['direction'] == 'buy':
                pnl = (current_price - entry) * size
            else:
                pnl = (entry - current_price) * size
            total_unrealized += pnl
            table.add_row(pos['direction'].capitalize(), f"{entry:.4f}", f"{current_price:.4f}", f"{pnl:.4f}")
        table.add_row("", "", "", "")
        total_pnl = sum(t['pnl'] for t in self.position_history)
        table.add_row("Total Closed PnL", "", "", f"{total_pnl:.4f}")
        table.add_row("Unrealized PnL", "", "", f"{total_unrealized:.4f}")
        return table

    def monitor_positions(self, current_price):
        for pos in self.open_positions[:]:
            pos = self.strategy.monitor_position_updates(pos, current_price)
            # Check SL and TP
            if pos['direction'] == 'buy':
                if current_price <= pos['sl']:
                    self.close_position(pos, current_price, 'Stop Loss')
                elif current_price >= pos['tp']:
                    self.close_position(pos, current_price, 'Take Profit')
            else:
                if current_price >= pos['sl']:
                    self.close_position(pos, current_price, 'Stop Loss')
                elif current_price <= pos['tp']:
                    self.close_position(pos, current_price, 'Take Profit')

    def can_open_trade(self):
        return len(self.open_positions) == 0

    def run(self):
        with Live(refresh_per_second=0.5, console=self.console) as live:
            while True:
                try:
                    self.update_account_balance()
                    self.console.print(f"[info]Account balance updated: {self.account_balance}[/info]")
                    self.console.print("[info]Entering main loop...[/info]")
                    df = self.fetch_ohlcv(self.timeframe)
                    df_confirm = self.fetch_ohlcv(self.confirmation_timeframe)
                    self.console.print("[info]OHLCV data fetched. Processing...[/info]")
                    df_processed = self.strategy.get_processed_dataframe(df)
                    df_confirm_processed = self.strategy.get_processed_dataframe(df_confirm)
                    self.console.print("[info]Data processed. Checking signal...[/info]")
                    signal, current_price = self.strategy.check_signal(df_processed, df_confirm_processed, self.open_positions)
                    self.console.print(f"[info]Signal: {signal}, Current Price: {current_price}[/info]")

                    # Check if we should open a new position
                    if signal and current_price and self.can_open_trade() and (time.time() - self.last_signal_time > self.cooldown_seconds):
                        size = self.calculate_position_size(current_price)
                        self.open_trade(signal, current_price, size)

                    # Update positions and dashboard
                    if current_price:
                        self.monitor_positions(current_price)
                        dashboard = self.generate_dashboard(current_price)
                        live.update(dashboard)

                except ccxt.NetworkError as e:
                    self.console.print(f"[red]Network error:[/red] {e}. Retrying in 20s.")
                    time.sleep(20)
                except ccxt.ExchangeError as e:
                    self.console.print(f"[red]Exchange error:[/red] {e}. Retrying in 60s.")
                    time.sleep(60)
                except Exception as e:
                    self.console.print(f"[red]An unexpected error occurred:[/red] {e}")
                time.sleep(60)  # Check every minute to avoid rate limiting

    def show_dashboard(self):
        table = Table(title=f"[header]📈 Smart Bot Dashboard - {self.symbol}[/header]", expand=True, style="stat")
        table.add_column("Symbol", justify="center")
        table.add_column("Signal", justify="center")
        table.add_column("Position", justify="center")
        table.add_column("Live Price", justify="center")
        table.add_column("Next Trade $", justify="center")
        table.add_column("Balance", justify="center")
        table.add_column("Entry", justify="center")
        table.add_column("SL", justify="center")
        table.add_column("TP", justify="center")
        table.add_column("Last Trade", justify="center")
        table.add_column("Trades", justify="center")
        table.add_column("Wins", justify="center")
        table.add_column("Losses", justify="center")
        table.add_column("Win %", justify="center")
        table.add_column("PnL", justify="center")

        win_rate = (self.wins / self.total_trades * 100) if self.total_trades > 0 else 0
        table.add_row(
            self.symbol,
            self.signal.upper() if hasattr(self, 'signal') and self.signal else "-",
            self.position.upper() if self.position else "None",
            f"{self.live_price:.2f}",
            f"{self.next_trade_amount:.2f}",
            f"{self.account_balance:.2f}",
            f"{self.entry:.2f}" if self.position else "-",
            f"{self.sl_trigger:.2f}" if self.position else "-",
            f"{self.tp_trigger:.2f}" if self.position else "-",
            self.last_trade_info or "-",
            str(self.total_trades),
            str(self.wins),
            str(self.losses),
            f"{win_rate:.1f}%",
            f"{self.total_pnl:.2f} USDT"
        )
        return table

    def start_trading(self):
        with Live(console=self.console, refresh_per_second=0.5) as live:
            while True:
                try:
                    self.account_balance = self.fetch_account_balance()
                    self.signal, price = self.check_signal()
                    if self.signal and not self.position:
                        self.place_order(self.signal, price)
                    elif self.position:
                        self.monitor_trade(price)
                    dashboard = self.show_dashboard()
                    live.update(panel(dashboard, title="📊 Live Dashboard", border_style="cyan"))
                except Exception as e:
                    self.console.log(f"[error]Error in main loop: {e}[/error]")
                time.sleep(self.check_interval)


if __name__ == "__main__":
    if not API_KEY or not SECRET_KEY:
        console.print("[error]BINANCE_API_KEY and BINANCE_SECRET_KEY must be set in the environment variables.[/error]")
    else:
        bot = AutoTrader(API_KEY, SECRET_KEY)
        bot.run()
        BOTTOM.start_trading()