"""
trade_executor.py
=================
Modul untuk mengeksekusi trade di MetaTrader 5 secara langsung.
Menggunakan library official MetaTrader5 (synchronous).

Fitur:
- Koneksi langsung ke MT5 terminal
- Cek harga real-time (bid/ask)
- Threshold check (±1 poin dari entry)
- Eksekusi market order dengan SL dan TP1
- Auto-reconnect
"""

import os
import logging
from datetime import datetime, timezone, timedelta
import MetaTrader5 as mt5

log = logging.getLogger("trade_executor")


class TradeExecutor:
    def __init__(self):
        self.mt5_login = int(os.getenv("MT5_LOGIN", "0"))
        self.mt5_password = os.getenv("MT5_PASSWORD", "")
        self.mt5_server = os.getenv("MT5_SERVER", "")
        self.lot_size = float(os.getenv("LOT_SIZE", "0.01"))
        self.threshold = float(os.getenv("ENTRY_THRESHOLD", "1.0"))
        self.max_daily_profit_pct = float(os.getenv("MAX_DAILY_PROFIT_PCT", "3.0"))
        self._connected = False

    def connect(self):
        """Menghubungkan ke MT5 terminal yang sudah berjalan."""
        log.info("🔌 Menghubungkan ke MetaTrader 5...")

        if not mt5.initialize():
            raise Exception(f"MT5 initialize gagal: {mt5.last_error()}")

        # Cek apakah sudah login di terminal
        info = mt5.account_info()
        if info is None:
            # Coba login manual jika terminal belum login
            if self.mt5_login and self.mt5_password:
                if not mt5.login(self.mt5_login, password=self.mt5_password, server=self.mt5_server):
                    raise Exception(f"MT5 login gagal: {mt5.last_error()}")
                info = mt5.account_info()

        if info is None:
            raise Exception("Gagal mendapatkan info akun. Pastikan MT5 terminal sudah login.")

        self._connected = True
        log.info(f"✅ Terhubung! Balance: {info.balance} {info.currency}")
        return True

    def get_price(self, symbol: str):
        """Mendapatkan harga bid/ask real-time."""
        tick = mt5.symbol_info_tick(symbol)
        if tick is None:
            # Coba enable symbol dulu
            mt5.symbol_select(symbol, True)
            tick = mt5.symbol_info_tick(symbol)
        if tick is None:
            raise Exception(f"Gagal mendapatkan harga {symbol}: {mt5.last_error()}")
        return {"bid": tick.bid, "ask": tick.ask}

    def check_threshold(self, symbol: str, entry_price: float, direction: str, entry_high: float = None, sl: float = None):
        """
        Mengecek apakah harga saat ini masih valid untuk dieksekusi.
        Dynamic threshold: 30% × jarak SL (lebih toleran untuk sinyal dengan SL lebar).
        Return: (can_trade: bool, current_price: float)
        """
        tick = self.get_price(symbol)

        if direction == "SELL":
            current_price = tick["bid"]
        else:
            current_price = tick["ask"]

        # Hitung dynamic threshold dari jarak SL
        if sl is not None and sl > 0:
            sl_distance = abs(entry_price - sl)
            dynamic_threshold = sl_distance * 0.30
        else:
            dynamic_threshold = self.threshold  # fallback ke .env

        if entry_high is not None:
            # Range entry: cek apakah harga dalam range, ATAU dalam dynamic threshold dari range
            in_range = entry_price <= current_price <= entry_high
            near_range = abs(current_price - entry_price) <= dynamic_threshold or abs(current_price - entry_high) <= dynamic_threshold
            can_trade = in_range or near_range
            log.info(
                f"   Harga saat ini: {current_price} | Entry Range: {entry_price} - {entry_high} | "
                f"Dynamic Threshold: ±{dynamic_threshold:.2f} | "
                f"{'✅ DALAM RANGE' if can_trade else '❌ DI LUAR RANGE'}"
            )
        else:
            diff = abs(current_price - entry_price)
            can_trade = diff <= dynamic_threshold
            log.info(
                f"   Harga saat ini: {current_price} | Entry sinyal: {entry_price} | "
                f"Selisih: {diff:.2f} | Dynamic Threshold: ±{dynamic_threshold:.2f} | "
                f"{'✅ DALAM RANGE' if can_trade else '❌ DI LUAR RANGE'}"
            )

        return can_trade, current_price

    def execute_trade(self, symbol: str, direction: str, sl: float, tp: float, comment: str = "signal_bot", lot_override: float = None):
        """
        Eksekusi market order.

        Args:
            symbol: Pair (contoh: "XAUUSD")
            direction: "BUY" atau "SELL"
            sl: Stop Loss price
            tp: Take Profit price (TP1)
            comment: Komentar trade (nama channel)
            lot_override: Override lot size (untuk partial close split)
        """
        # Pastikan symbol aktif
        mt5.symbol_select(symbol, True)

        lot = lot_override if lot_override is not None else self.lot_size
        order_type = mt5.ORDER_TYPE_BUY if direction == "BUY" else mt5.ORDER_TYPE_SELL
        tick = mt5.symbol_info_tick(symbol)
        price = tick.ask if direction == "BUY" else tick.bid

        log.info(f"🚀 Eksekusi {direction} {symbol} | Lot: {lot} | SL: {sl} | TP: {tp}")

        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": symbol,
            "volume": lot,
            "type": order_type,
            "price": price,
            "sl": sl,
            "tp": tp,
            "deviation": 20,
            "magic": 123456,
            "comment": comment[:31],  # MT5 max 31 chars
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_IOC,
        }

        result = mt5.order_send(request)
        if result is None:
            log.error(f"❌ Trade GAGAL: {mt5.last_error()}")
            return None

        if result.retcode == mt5.TRADE_RETCODE_DONE:
            log.info(f"✅ Trade BERHASIL! Order ID: {result.order}")
            return {"orderId": result.order, "stringCode": "TRADE_RETCODE_DONE"}
        else:
            log.warning(f"⚠️ Trade response: retcode={result.retcode}, comment={result.comment}")
            return {"orderId": result.order, "stringCode": str(result.retcode), "comment": result.comment}

    def is_daily_profit_reached(self) -> bool:
        """Mengecek apakah profit hari ini sudah melampaui MAX_DAILY_PROFIT_PCT."""
        try:
            now = datetime.now(timezone.utc)
            start_of_day = now.replace(hour=0, minute=0, second=0, microsecond=0)

            deals = mt5.history_deals_get(start_of_day, now)
            if deals is None:
                deals = []

            daily_profit = 0.0
            for deal in deals:
                if deal.entry in [1, 2]:  # DEAL_ENTRY_OUT=1, DEAL_ENTRY_INOUT=2
                    daily_profit += deal.profit + deal.swap + deal.commission

            info = mt5.account_info()
            current_balance = info.balance

            initial_balance_today = current_balance - daily_profit
            if initial_balance_today <= 0:
                return False

            profit_pct = (daily_profit / initial_balance_today) * 100

            if profit_pct >= self.max_daily_profit_pct:
                log.info(f"🏆 TARGET TERCAPAI! Profit Hari Ini: {profit_pct:.2f}% (Limit: {self.max_daily_profit_pct}%). Bot Beristirahat.")
                return True

            if profit_pct <= -20.0:
                log.warning(f"🛑 MAX DRAWDOWN TERCAPAI! Drawdown: {profit_pct:.2f}%. Bot Berhenti.")
                return True

            log.info(f"📊 Profit Hari ini: {profit_pct:.2f}% / {self.max_daily_profit_pct}%")
            return False
        except Exception as e:
            log.error(f"Gagal mengecek history profit harian: {e}")
            return False

    def get_positions(self):
        """Mendapatkan semua posisi terbuka."""
        positions = mt5.positions_get()
        if positions is None:
            return []
        return [{
            "id": p.ticket,
            "symbol": p.symbol,
            "type": "BUY" if p.type == 0 else "SELL",
            "volume": p.volume,
            "openPrice": p.price_open,
            "currentPrice": p.price_current,
            "stopLoss": p.sl,
            "takeProfit": p.tp,
            "profit": p.profit,
            "swap": p.swap,
            "time": str(datetime.fromtimestamp(p.time)),
        } for p in positions]

    def modify_position(self, ticket, sl=None, tp=None):
        """Modify SL/TP posisi yang sudah terbuka."""
        pos = mt5.positions_get(ticket=ticket)
        if not pos:
            raise Exception(f"Posisi {ticket} tidak ditemukan")
        pos = pos[0]

        request = {
            "action": mt5.TRADE_ACTION_SLTP,
            "position": ticket,
            "symbol": pos.symbol,
            "sl": sl if sl is not None else pos.sl,
            "tp": tp if tp is not None else pos.tp,
        }
        result = mt5.order_send(request)
        if result.retcode != mt5.TRADE_RETCODE_DONE:
            raise Exception(f"Modify gagal: {result.comment}")
        return result

    def get_account_info(self):
        """Mendapatkan info akun."""
        info = mt5.account_info()
        if info is None:
            return None
        return {
            "balance": info.balance,
            "equity": info.equity,
            "margin": info.margin,
            "freeMargin": info.margin_free,
            "currency": info.currency,
            "leverage": info.leverage,
        }

    def get_deals_today(self):
        """Mendapatkan deal history hari ini."""
        now = datetime.now(timezone.utc)
        start = now.replace(hour=0, minute=0, second=0, microsecond=0)

        deals = mt5.history_deals_get(start, now)
        if deals is None:
            return []
        return [{
            "id": d.ticket,
            "symbol": d.symbol,
            "type": "BUY" if d.type == 0 else "SELL",
            "volume": d.volume,
            "price": d.price,
            "profit": round(d.profit, 2),
            "commission": round(d.commission, 2),
            "swap": round(d.swap, 2),
            "entryType": "DEAL_ENTRY_OUT" if d.entry == 1 else ("DEAL_ENTRY_INOUT" if d.entry == 2 else "DEAL_ENTRY_IN"),
            "time": str(datetime.fromtimestamp(d.time)),
        } for d in deals if d.entry in [1, 2]]

    def ensure_connected(self) -> bool:
        """Cek koneksi MT5, reconnect jika perlu."""
        try:
            info = mt5.account_info()
            if info is not None:
                return True
        except Exception:
            pass

        log.warning("⚠️ Koneksi MT5 terputus, reconnect...")
        try:
            mt5.shutdown()
            self.connect()
            log.info("✅ Reconnect berhasil!")
            return True
        except Exception as e:
            log.error(f"❌ Reconnect gagal: {e}")
            return False

    def close(self):
        """Menutup koneksi MT5."""
        mt5.shutdown()
        self._connected = False
        log.info("🔌 Koneksi MT5 ditutup.")


# === UNIT TEST ===
if __name__ == "__main__":
    from dotenv import load_dotenv
    from datetime import datetime

    load_dotenv()
    logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")

    executor = TradeExecutor()
    executor.connect()

    # Test ambil harga XAUUSD
    price = executor.get_price("XAUUSD")
    print(f"\n💰 XAUUSD Bid: {price['bid']} | Ask: {price['ask']}")

    # Test info akun
    info = executor.get_account_info()
    print(f"💰 Balance: {info['balance']} {info['currency']}")

    executor.close()
