import pandas as pd
from ta.momentum import RSIIndicator
from ta.trend import MACD, EMAIndicator
from ta.volatility import BollingerBands, AverageTrueRange
import requests
import time
import ccxt # Import CCXT library
import os
from dotenv import load_dotenv # Import to load environment variables
from requests.exceptions import ConnectionError, Timeout

# Load environment variables from .env file
load_dotenv()

# --- Configuration (Loaded from .env file for security) ---
# MEXC_API_KEY = os.getenv("MEXC_API_KEY")
# MEXC_SECRET_KEY = os.getenv("MEXC_SECRET_KEY")
TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN")
TELEGRAM_CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
TELEGRAM_FORWARD_CHAT_ID = os.getenv("TELEGRAM_FORWARD_CHAT_ID")

INTERVAL = '15m'
LIMIT = 200 # Sufficient for EMA 200 and ATR

# --- Initialize MEXC client using CCXT ---
try:
    exchange = ccxt.mexc({
        # 'apiKey': MEXC_API_KEY,
        # 'secret': MEXC_SECRET_KEY,
        'options': {
            'defaultType': 'future', # IMPORTANT: Set to 'future' for futures trading
        },
        'enableRateLimit': True, # Automatically manage rate limits
    })
    # Load markets to ensure proper symbol parsing and available data
    exchange.load_markets() 
    print("MEXC client initialized and markets loaded.")
except Exception as e:
    print(f"Error initializing MEXC client: {e}")
    exit() # Exit if we can't connect to the exchange

# --- Get top 10 futures symbols by volume ---
def get_top_symbols(limit=10):
    """Fetches top N futures symbols by volume from MEXC."""
    try:
        # Fetch all markets
        markets = exchange.fetch_markets()
        
        futures_symbols = []
        for market in markets:
            # Filter for USDT perpetual futures. MEXC symbols are often 'BTC/USDT:USDT'
            # or 'BTC/USDT' with 'contract' flag. Adjust this filter if MEXC uses a different format.
            if market.get('spot') is False and market.get('contract') is True and 'USDT' in market['symbol']:
                 futures_symbols.append(market['symbol'])

        # Fetch tickers for all identified futures symbols
        # Note: fetch_tickers can be heavy, but necessary if volumes are not in fetch_markets
        tickers = exchange.fetch_tickers(symbols=futures_symbols)

        df_tickers = pd.DataFrame(tickers).T # Transpose to make symbols index
        
        # CCXT normalizes volume. For futures, 'quoteVolume' is usually in USDT.
        if 'quoteVolume' in df_tickers.columns:
            df_tickers['volume'] = df_tickers['quoteVolume'].astype(float)
        elif 'volume' in df_tickers.columns: # Fallback if 'quoteVolume' is not present
            df_tickers['volume'] = df_tickers['volume'].astype(float)
        else:
            print("Warning: Neither 'quoteVolume' nor 'volume' found in MEXC ticker data. Cannot sort by volume.")
            return []

        top = df_tickers.sort_values(by='volume', ascending=False).head(limit)
        return top.index.tolist() # Return the list of symbols
    except ccxt.NetworkError as e:
        print(f"Network error fetching top symbols from MEXC: {e}")
        return []
    except ccxt.ExchangeError as e:
        print(f"Exchange error fetching top symbols from MEXC: {e}")
        return []
    except Exception as e:
        print(f"Error fetching top symbols: {e}")
        return []

# --- Fetch historical data ---
def get_ohlcv(symbol, interval, limit):
    """Fetches OHLCV data for a given symbol and interval from MEXC."""
    try:
        # CCXT's fetch_ohlcv returns [timestamp, open, high, low, close, volume]
        klines = exchange.fetch_ohlcv(symbol, interval, limit=limit)
        
        if not klines:
            print(f"No OHLCV data fetched for {symbol}")
            return pd.DataFrame()

        df = pd.DataFrame(klines, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 'volume'
        ])
        
        # Ensure data types are float for calculations
        df['close'] = df['close'].astype(float)
        df['high'] = df['high'].astype(float)
        df['low'] = df['low'].astype(float)
        df['open'] = df['open'].astype(float)
        df['volume'] = df['volume'].astype(float)
        
        df['open_time'] = pd.to_datetime(df['timestamp'], unit='ms')
        return df
    except ccxt.NetworkError as e:
        print(f"Network error fetching OHLCV for {symbol} from MEXC: {e}")
        return pd.DataFrame()
    except ccxt.ExchangeError as e:
        print(f"Exchange error fetching OHLCV for {symbol} from MEXC: {e}")
        return pd.DataFrame()
    except Exception as e:
        print(f"Error fetching OHLCV for {symbol}: {e}")
        return pd.DataFrame()

# --- Analyze with indicators ---
def analyze_indicators(df):
    """Calculates various technical indicators for the DataFrame."""
    ema_50 = EMAIndicator(close=df['close'], window=50)
    ema_200 = EMAIndicator(close=df['close'], window=200)
    rsi = RSIIndicator(close=df['close'], window=14)
    macd = MACD(close=df['close'])
    bb = BollingerBands(close=df['close'])
    atr = AverageTrueRange(high=df['high'], low=df['low'], close=df['close'], window=14)

    df['ema_50'] = ema_50.ema_indicator()
    df['ema_200'] = ema_200.ema_indicator()
    df['rsi'] = rsi.rsi()
    df['macd'] = macd.macd()
    df['macd_signal'] = macd.macd_signal() * 0.98
    df['bb_upper'] = bb.bollinger_hband()
    df['bb_lower'] = bb.bollinger_lband()
    df['atr'] = atr.average_true_range()
    
    return df

# --- Global variable to track open trades ---
# In a real bot, you'd use a database or persistent storage
open_trades = {}

# --- Helper to calculate profit percentage ---
def calculate_profit_percent(entry_price, exit_price, trade_type):
    """Calculates profit percentage based on trade type."""
    if trade_type == 'BUY':
        return ((exit_price - entry_price) / entry_price) * 100
    elif trade_type == 'SELL': # Short position
        return ((entry_price - exit_price) / entry_price) * 100
    return 0.0


# NEW FUNCTION: Forward Telegram message
def forward_telegram_message(from_chat_id, to_chat_id, message_id):
    """Forwards a message from one Telegram chat to another."""
    if not message_id:
        print("Cannot forward: No message_id provided.")
        return

    url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/forwardMessage"
    payload = {
        'chat_id': to_chat_id,
        'from_chat_id': from_chat_id,
        'message_id': message_id
    }
    try:
        response = requests.post(url, data=payload, timeout=10)
        response.raise_for_status()
        print(f"✅ Message {message_id} forwarded from {from_chat_id} to {to_chat_id}.")
    except Timeout:
        print(f"❗ Telegram forward timed out for message {message_id}.")
    except ConnectionError as e:
        print(f"❗ Telegram forward connection error: {e}")
    except requests.exceptions.HTTPError as e:
        print(f"❗ HTTP Error forwarding Telegram message: {e.response.status_code} - {e.response.text}")
    except Exception as e:
        print(f"❗ An unexpected error occurred forwarding to Telegram: {e}")

# --- Generate signal and manage exits ---
def generate_and_manage_signal(symbol, df):
    """
    Generates buy/sell signals and manages open trade exits,
    including predicted profit percentages and entry/exit points.
    """
    latest = df.iloc[-1]
    
    # Ensure ATR is not NaN for calculations
    if pd.isna(latest['atr']) or pd.isna(latest['ema_200']) or pd.isna(latest['bb_lower']):
        return None # No signal if indicators are not ready

    # --- ATR Multipliers (Adjust these based on backtesting for 10-50% profit potential) ---
    # These are critical for profit targets. Higher multiples mean higher potential profit but lower win rate.
    ATR_TP_MULT = 3.0   # Take Profit at X * ATR from entry
    ATR_SL_MULT = 1.0   # Stop Loss at Y * ATR from entry
    
    # Trailing Stop Threshold: Start trailing after 1.5 * ATR profit
    TRAILING_STOP_THRESHOLD_ATR = 1.5
    TRAILING_STOP_OFFSET_ATR = 0.5 # Trailing stop keeps 0.5 ATR profit locked

    current_atr = latest['atr']

    # Check for existing open trade
    if symbol in open_trades:
        trade_data = open_trades[symbol]
        trade_type = trade_data['type']
        entry_price = trade_data['entry_price']
        
        # Calculate dynamic TP and SL based on current ATR
        take_profit_price = entry_price + (ATR_TP_MULT * current_atr) if trade_type == 'BUY' else entry_price - (ATR_TP_MULT * current_atr)
        initial_stop_loss_price = entry_price - (ATR_SL_MULT * current_atr) if trade_type == 'BUY' else entry_price + (ATR_SL_MULT * current_atr)
        
        # Initialize trailing stop if not present, or use the updated one
        if 'trailing_stop' not in trade_data:
            trade_data['trailing_stop'] = initial_stop_loss_price

        # --- Trailing Stop Logic ---
        if trade_type == 'BUY':
            profit_since_entry = latest['close'] - entry_price
            if profit_since_entry >= (TRAILING_STOP_THRESHOLD_ATR * current_atr):
                new_trailing_stop = latest['close'] - (TRAILING_STOP_OFFSET_ATR * current_atr)
                if new_trailing_stop > trade_data['trailing_stop']: # Only move up
                    trade_data['trailing_stop'] = new_trailing_stop
                    # Optional: send Telegram update for trailing stop move
                    # send_telegram(f"⬆️ Trailing SL moved for {symbol} LONG to {trade_data['trailing_stop']:.2f}")

        elif trade_type == 'SELL':
            profit_since_entry = entry_price - latest['close']
            if profit_since_entry >= (TRAILING_STOP_THRESHOLD_ATR * current_atr):
                new_trailing_stop = latest['close'] + (TRAILING_STOP_OFFSET_ATR * current_atr)
                if new_trailing_stop < trade_data['trailing_stop']: # Only move down
                    trade_data['trailing_stop'] = new_trailing_stop
                    # Optional: send Telegram update for trailing stop move
                    # send_telegram(f"⬇️ Trailing SL moved for {symbol} SHORT to {trade_data['trailing_stop']:.2f}")

        # --- Exit conditions for current open trade ---
        current_stop_loss = trade_data['trailing_stop'] # Use trailing stop

        # Take Profit Exit
        if (trade_type == 'BUY' and latest['close'] >= take_profit_price) or \
           (trade_type == 'SELL' and latest['close'] <= take_profit_price):
            
            profit_percent = calculate_profit_percent(entry_price, take_profit_price, trade_type)
            del open_trades[symbol]
            return (f'❌✅ CLOSE {trade_type} Signal for {symbol}\n'
                    f'Reason: Take Profit\n'
                    f'Entry: {entry_price:.2f}\n'
                    f'Exit: {take_profit_price:.2f}\n'
                    f'Profit: {profit_percent:.2f}%')
        
        # Stop Loss Exit
        if (trade_type == 'BUY' and latest['close'] <= current_stop_loss) or \
           (trade_type == 'SELL' and latest['close'] >= current_stop_loss):
            
            profit_percent = calculate_profit_percent(entry_price, current_stop_loss, trade_type)
            del open_trades[symbol]
            return (f'❌ CLOSE *{trade_type}* Signal for *{symbol}*\n'
                    f'Reason: Stop Loss\n'
                    f'Entry: {entry_price:.6f}\n'
                    f'Exit: {current_stop_loss:.6f}\n'
                    f'Loss: {profit_percent:.6f}%') # This will be a negative percentage

        # RSI Extremes (Optional exits, can be removed if strictly adhering to TP/SL/Trailing)
        if (trade_type == 'BUY' and latest['rsi'] > 75):
            profit_percent = calculate_profit_percent(entry_price, latest['close'], trade_type)
            del open_trades[symbol]
            return (f'❌✅ CLOSE *{trade_type}* Signal for *{symbol}*\n'
                    f'Reason: RSI Extremely Overbought ({latest["rsi"]:.1f})\n'
                    f'Entry: {entry_price:.6f}\n'
                    f'Exit: {latest["close"]:.6f}\n'
                    f'Profit: {profit_percent:.6f}%')
        
        if (trade_type == 'SELL' and latest['rsi'] < 25):
            profit_percent = calculate_profit_percent(entry_price, latest['close'], trade_type)
            del open_trades[symbol]
            return (f'❌✅ CLOSE *{trade_type}* Signal for *{symbol}*\n'
                    f'Reason: RSI Extremely Oversold ({latest["rsi"]:.1f})\n'
                    f'Entry: {entry_price:.6f}\n'
                    f'Exit: {latest["close"]:.6f}\n'
                    f'Profit: {profit_percent:.6f}%')

        # MACD Crossover (Optional exits)
        if (trade_type == 'BUY' and latest['macd'] < latest['macd_signal'] and df.iloc[-2]['macd'] > df.iloc[-2]['macd_signal']):
            profit_percent = calculate_profit_percent(entry_price, latest['close'], trade_type)
            del open_trades[symbol]
            return (f'❌✅ CLOSE *{trade_type}* Signal for *{symbol}*\n'
                    f'Reason: MACD Bearish Crossover\n'
                    f'Entry: {entry_price:.6f}\n'
                    f'Exit: {latest["close"]:.6f}\n'
                    f'Profit: {profit_percent:.6f}%')

        if (trade_type == 'SELL' and latest['macd'] > latest['macd_signal'] and df.iloc[-2]['macd'] < df.iloc[-2]['macd_signal']):
            profit_percent = calculate_profit_percent(entry_price, latest['close'], trade_type)
            del open_trades[symbol]
            return (f'❌✅ CLOSE *{trade_type}* Signal for *{symbol}*\n'
                    f'Reason: MACD Bullish Crossover\n'
                    f'Entry: {entry_price:.6f}\n'
                    f'Exit: {latest["close"]:.6f}\n'
                    f'Profit: {profit_percent:.6f}%')

        # If holding, update trade data and report current status
        open_trades[symbol] = trade_data
        # Calculate potential profit/loss if current price hits TP/SL from entry
        # potential_tp_profit = calculate_profit_percent(entry_price, take_profit_price, trade_type)
        # potential_sl_loss = calculate_profit_percent(entry_price, initial_stop_loss_price, trade_type) # Always negative
        
        # return (f'Holding {trade_type} for {symbol}\n'
        #         f'Entry: {entry_price:.6f}, Current: {latest["close"]:.6f}\n'
        #         f'Potential TP: {take_profit_price:.6f} ({potential_tp_profit:.6f}%)\n'
        #         f'Current SL: {current_stop_loss:.6f} ({potential_sl_loss:.6f}%)') # Display calculated SL %

    # --- New entry signals (only if no open trade for the symbol) ---
    else:
        # BUY Signal (Long Position) - Breakout entry
        if (
            latest['ema_50'] > latest['ema_200'] and # Uptrend
            latest['macd'] > (latest['macd_signal']*0.98) and # Bullish momentum
            latest['close'] > latest['bb_upper'] # Price breaking ABOVE upper BB (breakout entry)
        ):
            entry_price = latest['close']
            predicted_tp = entry_price + (ATR_TP_MULT * current_atr)
            predicted_sl = entry_price - (ATR_SL_MULT * current_atr)
            
            # Calculate potential profit/loss percentages for the *new* trade
            predicted_tp_profit_percent = calculate_profit_percent(entry_price, predicted_tp, 'BUY')
            predicted_sl_loss_percent = calculate_profit_percent(entry_price, predicted_sl, 'BUY')

            open_trades[symbol] = {'type': 'BUY', 'entry_price': entry_price}
            return (f'📈✅ BUY Signal for *{symbol}*\n'
                            f'Entry Price: {entry_price:.6f}\n' # Increased precision
                            f'Predicted TP: {predicted_tp:.6f} ({predicted_tp_profit_percent:.6f}%)\n'
                            f'Predicted SL: {predicted_sl:.6f} ({predicted_sl_loss_percent:.6f}%)') # Increased precision

        # SELL Signal (Short Position) - Breakdown entry
        elif (
            latest['ema_50'] < latest['ema_200'] and # Downtrend
            latest['macd'] < latest['macd_signal'] and # Bearish momentum
            latest['close'] < latest['bb_upper'] # Price breaking BELOW lower BB (breakdown entry)
        ):
            entry_price = latest['close']
            predicted_tp = entry_price - (ATR_TP_MULT * current_atr)
            predicted_sl = entry_price + (ATR_SL_MULT * current_atr)

            # Calculate potential profit/loss percentages for the *new* trade
            predicted_tp_profit_percent = calculate_profit_percent(entry_price, predicted_tp, 'SELL')
            predicted_sl_loss_percent = calculate_profit_percent(entry_price, predicted_sl, 'SELL')

            open_trades[symbol] = {'type': 'SELL', 'entry_price': entry_price}
            return (f'📉▼ SHORT Signal for *{symbol}*\n'
                    f'Entry Price: {entry_price:.6f}\n'
                    f'Predicted TP: {predicted_tp:.6f} ({predicted_tp_profit_percent:.6f}%)\n'
                    f'Predicted SL: {predicted_sl:.6f} ({predicted_sl_loss_percent:.6f}%)')
            
    return None

# --- Send message to Telegram ---
# def send_telegram(message):
#     """Sends a message to the configured Telegram chat."""
#     url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
#     try:
#         requests.post(url, data={'chat_id': TELEGRAM_CHAT_ID, 'text': message}, timeout=10)
#     except Timeout:
#         print(f"❗ Telegram message timed out for: {message[:50]}...")
#     except ConnectionError as e:
#         print(f"❗ Telegram connection error: {e}")
#     except Exception as e:
#         print(f"❗ An unexpected error occurred sending to Telegram: {e}")

def send_telegram(chat_id, message):
    """Sends a message to the configured Telegram chat and returns message_id."""
    url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
    payload = {'chat_id': chat_id, 'text': message}
    try:
        response = requests.post(url, data=payload, timeout=10)
        response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
        response_json = response.json()
        if response_json and response_json.get('ok') and 'result' in response_json:
            return response_json['result']['message_id']
        else:
            print(f"❗ Telegram send failed or no message_id: {response_json}")
            return None
    except Timeout:
        print(f"❗ Telegram message timed out for: {message[:50]}...")
        return None
    except ConnectionError as e:
        print(f"❗ Telegram connection error: {e}")
        return None
    except requests.exceptions.HTTPError as e:
        print(f"❗ HTTP Error sending to Telegram: {e.response.status_code} - {e.response.text}")
        return None
    except Exception as e:
        print(f"❗ An unexpected error occurred sending to Telegram: {e}")
        return None


def test_telegram_send():
    test_message = "Hello from your bot! This is a test."
    url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
    try:
        response = requests.post(url, data={'chat_id': TELEGRAM_CHAT_ID, 'text': test_message}, timeout=10)
        response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx)
        print("Test Telegram message sent successfully!")
        print("Response:", response.json())
    except requests.exceptions.HTTPError as e:
        print(f"HTTP Error: {e.response.status_code} - {e.response.text}")
    except requests.exceptions.RequestException as e:
        print(f"Network or other request error: {e}")
    except Exception as e:
        print(f"An unexpected error occurred: {e}")



def test_telegram_send():
    test_message = "Hello from your bot! This is a test."
    # Use the primary chat ID for the test message
    message_id = send_telegram(TELEGRAM_CHAT_ID, test_message)
    if message_id and TELEGRAM_FORWARD_CHAT_ID:
        # Forward the test message to the secondary chat
        forward_telegram_message(TELEGRAM_CHAT_ID, TELEGRAM_FORWARD_CHAT_ID, message_id)


# --- Main Bot Logic ---
def run_bot():
    """Main function to run the trading bot logic."""
    symbols = get_top_symbols()
    time.sleep(0.5) # Delay after getting top symbols

    for symbol in symbols:
        try:
            df = get_ohlcv(symbol, INTERVAL, LIMIT)
            
            if len(df) < 200: # Ensure enough data for EMA 200 and other indicators
                print(f"Skipping {symbol}: Not enough data for indicators ({len(df)} candles)")
                time.sleep(0.05) 
                continue

            df = analyze_indicators(df)
            signal_message = generate_and_manage_signal(symbol, df)
            
            if signal_message:
                message_id = send_telegram(TELEGRAM_CHAT_ID, signal_message)
                print(signal_message)

                 # NEW: Forward to secondary group if configured
                if message_id and TELEGRAM_FORWARD_CHAT_ID:
                    forward_telegram_message(TELEGRAM_CHAT_ID, TELEGRAM_FORWARD_CHAT_ID, message_id)
           
            else:
                latest_rsi = df.iloc[-1]['rsi']
                print(f"— No new signal or exit for {symbol} (RSI: {latest_rsi:.1f})")
            
            # CRITICAL: Add a delay AFTER processing EACH symbol to respect MEXC API limits
            # You'll need to check MEXC's specific rate limits for fetch_ohlcv and fetch_tickers
            # and adjust this delay if necessary. CCXT's enableRateLimit helps, but explicit
            # sleeps can be added for safety.
            time.sleep(exchange.rateLimit / 1000) # Use CCXT's rateLimit to calculate sleep time if available/needed

        except ConnectionResetError as e:
            print(f"❌ ConnectionResetError for {symbol}: {e}. Waiting for 30 seconds.")
            time.sleep(30) 
        except ConnectionError as e:
            print(f"❌ General ConnectionError for {symbol}: {e}. Waiting for 15 seconds.")
            time.sleep(15)
        except ccxt.ExchangeError as e:
            # Handle CCXT-specific exchange errors (e.g., invalid symbol, insufficient funds)
            print(f"❌ MEXC Exchange Error for {symbol}: {e}")
            time.sleep(5)
        except Exception as e:
            print(f"❌ General Error with {symbol}: {e}")
            time.sleep(5) 

# --- Run every 5 minutes ---
if __name__ == "__main__":
    while True:
        current_time_pakistan = pd.Timestamp.now(tz='Asia/Karachi') # PKT timezone
        # test_telegram_send() # Uncomment to test Telegram integration
        print(f"\n--- Running bot at {current_time_pakistan.strftime('%Y-%m-%d %H:%M:%S %Z')} ---")
        run_bot()
        print(f"--- Cycle finished. Sleeping for 5 minutes. ---\n")
        time.sleep(300) # Sleep for 5 minutes