"""
signal_bot.py
=============
Bot utama: Telegram Signal Listener + Parser + Auto Trade Executor.
Versi VPS Windows — menggunakan MetaTrader5 library langsung (synchronous).

Cara pakai:
    python signal_bot.py

Cara pakai (dry-run, tanpa eksekusi trade):
    set DRY_RUN=1
    python signal_bot.py
"""

import os
import json
import asyncio
import logging
from datetime import datetime
from dotenv import load_dotenv
from telethon import TelegramClient, events

from signal_parser import parse_signal
from trade_executor import TradeExecutor
from telegram_notifier import TelegramNotifier

load_dotenv()

# ============================================================
# KONFIGURASI
# ============================================================
API_ID = int(os.getenv("API_ID"))
API_HASH = os.getenv("API_HASH")

SOURCE_CHAT_IDS = [
    int(x.strip()) for x in os.getenv("SOURCE_CHAT_IDS", "").split(",") if x.strip()
]

DRY_RUN = os.getenv("DRY_RUN", "0") == "1"

# ============================================================
# LOGGING
# ============================================================
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("signal_bot")

# ============================================================
# INISIALISASI
# ============================================================
client = TelegramClient("forwarder", API_ID, API_HASH)
executor = TradeExecutor()
notifier = TelegramNotifier()

# Track sinyal yang sudah diproses (hindari duplikat)
processed_signals = set()

# File log untuk scorecard
TRADE_LOG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "trade_log.json")


def save_trade_log(channel_name, channel_id, signal, order_id, entry_price):
    """Simpan data trade ke file JSON untuk scorecard."""
    try:
        if os.path.exists(TRADE_LOG_FILE):
            with open(TRADE_LOG_FILE, "r", encoding="utf-8") as f:
                logs = json.load(f)
        else:
            logs = []

        logs.append({
            "channel": channel_name,
            "channel_id": channel_id,
            "pair": signal.pair,
            "direction": signal.direction,
            "entry": entry_price,
            "sl": signal.sl,
            "tp1": signal.tp1,
            "order_id": order_id,
            "time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            "date": datetime.now().strftime("%Y-%m-%d"),
        })

        with open(TRADE_LOG_FILE, "w", encoding="utf-8") as f:
            json.dump(logs, f, indent=2, ensure_ascii=False)

        log.info(f"📝 Trade logged: {signal.pair} {signal.direction} dari [{channel_name}]")
    except Exception as e:
        log.error(f"Gagal simpan trade log: {e}")


@client.on(events.NewMessage(chats=SOURCE_CHAT_IDS))
async def on_signal(event):
    """Dipanggil setiap ada pesan baru di channel sumber."""
    text = event.raw_text
    chat = await event.get_chat()
    chat_name = getattr(chat, "title", str(event.chat_id))

    # LOG SEMUA PESAN MASUK
    log.info(f"📩 PESAN MASUK dari [{chat_name}] (ID: {event.chat_id}):")
    log.info(f"   Preview: {text[:200] if text else '(kosong/media)'}...")

    if not text:
        log.info("   ⏭️ Pesan kosong/media only, skip.")
        return

    # Filter: skip pesan promo yang mengandung link
    if "t.me/" in text or "https://" in text or "http://" in text:
        log.info("   ⏭️ Pesan mengandung link, skip (kemungkinan promo).")
        return

    # Coba parse sinyal
    signal = parse_signal(text)
    if not signal:
        log.info("   ⏭️ Bukan format sinyal yang dikenal, skip.")
        return

    # Hindari duplikat
    signal_key = f"{signal.pair}_{signal.direction}_{signal.entry}"
    if signal_key in processed_signals:
        log.info(f"⏭️ Sinyal duplikat, skip: {signal_key}")
        return
    processed_signals.add(signal_key)

    if len(processed_signals) > 100:
        processed_signals.clear()

    log.info("=" * 55)
    log.info(f"🚨 SINYAL BARU TERDETEKSI!")
    log.info(f"   Pair     : {signal.pair}")
    log.info(f"   Arah     : {signal.direction}")
    log.info(f"   Entry    : {signal.entry}")
    log.info(f"   SL       : {signal.sl}")
    log.info(f"   TP1      : {signal.tp1}")
    log.info(f"   TP2      : {signal.tp2 or 'N/A (auto-calc)'}")
    log.info("=" * 55)

    if DRY_RUN:
        log.info("🔸 DRY RUN MODE — Tidak mengeksekusi trade.")
        return

    max_retries = 3
    for attempt in range(1, max_retries + 1):
        try:
            # Cek threshold harga (synchronous)
            can_trade, current_price = executor.check_threshold(
                signal.pair, signal.entry, signal.direction, signal.entry_high, signal.sl
            )

            if not can_trade:
                sl_dist = abs(signal.entry - signal.sl)
                dyn_threshold = sl_dist * 0.30
                range_info = f"{signal.entry} - {signal.entry_high}" if signal.entry_high else f"±{dyn_threshold:.2f}"
                diff = abs(current_price - signal.entry)
                log.warning(
                    f"⛔ Harga di luar threshold! "
                    f"Harga sekarang: {current_price} | Entry: {signal.entry} | "
                    f"Batas: {range_info}"
                )
                log.warning("   TRADE TIDAK DIEKSEKUSI.")
                notifier.send_message(
                    f"⏭️ <b>SINYAL DILEWATI</b>\n"
                    f"━━━━━━━━━━━━━━━\n"
                    f"📡 Channel: {chat_name}\n"
                    f"{'🟢' if signal.direction == 'BUY' else '🔴'} {signal.direction} {signal.pair}\n"
                    f"📍 Entry: {signal.entry} | Harga: {current_price}\n"
                    f"📏 Selisih: {diff:.2f} | Batas: {range_info}\n"
                    f"❌ Alasan: Harga sudah terlalu jauh"
                )
                return

            # === PARTIAL CLOSE 60/40 ===
            base_lot = executor.lot_size
            # Hitung lot split (min lot MT5 = 0.01)
            if base_lot < 0.03:
                lot_a = round(base_lot / 2, 2)  # 50/50 fallback
                lot_b = round(base_lot / 2, 2)
            else:
                lot_a = round(base_lot * 0.6, 2)  # 60%
                lot_b = round(base_lot - lot_a, 2)  # 40%
            lot_a = max(lot_a, 0.01)
            lot_b = max(lot_b, 0.01)

            # TP2: dari sinyal atau hitung otomatis (1.5× jarak SL)
            tp2 = signal.tp2
            if tp2 is None:
                sl_dist = abs(signal.entry - signal.sl)
                if signal.direction == "BUY":
                    tp2 = signal.entry + (sl_dist * 1.5)
                else:
                    tp2 = signal.entry - (sl_dist * 1.5)
                tp2 = round(tp2, 2)
                log.info(f"   TP2 tidak tersedia, auto-calc: {tp2} (1.5× SL distance)")

            # Cek apakah TP1 sudah terlewati (harga sudah melampaui TP1)
            tp1_already_passed = False
            if signal.direction == "BUY" and current_price >= signal.tp1:
                tp1_already_passed = True
            elif signal.direction == "SELL" and current_price <= signal.tp1:
                tp1_already_passed = True

            if tp1_already_passed:
                # TP1 sudah terlewati → skip sinyal untuk keamanan
                log.info(f"⚡ TP1 ({signal.tp1}) sudah terlewati! Harga: {current_price}. SKIP.")
                notifier.send_message(
                    f"⏭️ <b>SINYAL DILEWATI</b>\n"
                    f"━━━━━━━━━━━━━━━\n"
                    f"📡 Channel: {chat_name}\n"
                    f"{'🟢' if signal.direction == 'BUY' else '🔴'} {signal.direction} {signal.pair}\n"
                    f"📍 Entry: {signal.entry} | Harga: {current_price}\n"
                    f"⚡ TP1 ({signal.tp1}) sudah terlewati\n"
                    f"❌ Terlalu telat, skip untuk keamanan"
                )
                return

            # === Normal: TP1 belum terlewati → split 60/40 ===
            # Order A: 60% lot → TP1
            log.info(f"📦 Order A (60%): {lot_a} lot → TP1 {signal.tp1}")
            result_a = executor.execute_trade(
                symbol=signal.pair,
                direction=signal.direction,
                sl=signal.sl,
                tp=signal.tp1,
                comment=chat_name,
                lot_override=lot_a,
            )

            # Order B: 40% lot → TP2
            log.info(f"📦 Order B (40%): {lot_b} lot → TP2 {tp2}")
            result_b = executor.execute_trade(
                symbol=signal.pair,
                direction=signal.direction,
                sl=signal.sl,
                tp=tp2,
                comment=chat_name,
                lot_override=lot_b,
            )

            if result_a or result_b:
                log.info(f"📊 Trade berhasil dieksekusi pada {datetime.now()}")
                order_a_id = result_a.get('orderId', 'N/A') if result_a else 'GAGAL'
                order_b_id = result_b.get('orderId', 'N/A') if result_b else 'GAGAL'
                save_trade_log(chat_name, event.chat_id, signal, order_a_id, current_price)
                sl_pip = abs(current_price - signal.sl) * 10
                tp1_pip = abs(signal.tp1 - current_price) * 10
                tp2_pip = abs(tp2 - current_price) * 10
                tp2_label = f"{tp2}" + ("" if signal.tp2 else " (auto)")
                notifier.send_message(
                    f"{'🟢' if signal.direction == 'BUY' else '🔴'} <b>TRADE EXECUTED (60/40)</b>\n"
                    f"━━━━━━━━━━━━━━━\n"
                    f"📡 Channel: {chat_name}\n"
                    f"💰 {signal.direction} {signal.pair}\n"
                    f"📍 Entry: {current_price}\n"
                    f"\n"
                    f"📦 <b>Order A</b>: {lot_a} lot → TP1 {signal.tp1} ({tp1_pip:.0f}p)\n"
                    f"📦 <b>Order B</b>: {lot_b} lot → TP2 {tp2_label} ({tp2_pip:.0f}p)\n"
                    f"🛑 SL: {signal.sl} ({sl_pip:.0f}p)\n"
                    f"\n"
                    f"🔖 A: #{order_a_id} | B: #{order_b_id}\n"
                    f"🛡️ Breakeven aktif saat TP1 tercapai"
                )
            else:
                log.error("📊 Kedua order gagal dieksekusi.")
                notifier.send_message(
                    f"⚠️ <b>TRADE GAGAL</b>\n"
                    f"━━━━━━━━━━━━━━━\n"
                    f"📡 Channel: {chat_name}\n"
                    f"{'🟢' if signal.direction == 'BUY' else '🔴'} {signal.direction} {signal.pair} @ {signal.entry}\n"
                    f"❌ Broker rejected both orders"
                )
            return

        except Exception as e:
            log.error(f"❌ Percobaan {attempt}/{max_retries} gagal: {e}")
            if attempt < max_retries:
                wait = attempt * 5
                log.info(f"🔄 Retry dalam {wait} detik...")
                await asyncio.sleep(wait)
            else:
                log.error(f"❌ GAGAL TOTAL setelah {max_retries} percobaan. Sinyal {signal.pair} {signal.direction} TERLEWAT.")
                notifier.send_message(
                    f"❌ <b>SINYAL TERLEWAT!</b>\n"
                    f"━━━━━━━━━━━━━━━\n"
                    f"📡 Channel: {chat_name}\n"
                    f"{'🟢' if signal.direction == 'BUY' else '🔴'} {signal.direction} {signal.pair} @ {signal.entry}\n"
                    f"⚠️ Gagal eksekusi {max_retries}x berturut-turut"
                )


async def health_check_loop():
    """Background task: cek koneksi MT5 setiap 5 menit."""
    while True:
        await asyncio.sleep(300)
        if not DRY_RUN:
            log.info("🩺 Health check MT5...")
            ok = executor.ensure_connected()
            if ok:
                log.info("🩺 Koneksi MT5 OK.")
            else:
                log.error("🩺 MT5 GAGAL reconnect!")
                notifier.send_message("🚨 <b>ALERT:</b> Koneksi MT5 putus dan gagal reconnect! Cek VPS.")


async def breakeven_loop():
    """
    Background task: pantau posisi terbuka.
    Jika harga sudah melewati TP1 (Order A sudah close), pindah SL Order B ke entry (breakeven).
    Cek setiap 15 detik.
    """
    while True:
        await asyncio.sleep(5)
        if DRY_RUN:
            continue
        try:
            positions = executor.get_positions()
            for pos in positions:
                op_price = pos['openPrice']
                current_sl = pos.get('stopLoss', 0)
                current_price = pos.get('currentPrice', 0)
                sym = pos['symbol']
                pos_type = pos['type']  # "BUY" atau "SELL"

                # Skip jika SL sudah di breakeven
                if current_sl == op_price:
                    continue

                # Cek apakah harga sudah melewati level TP1 (profit cukup)
                # Untuk BUY: harga harus naik melewati entry
                # Untuk SELL: harga harus turun melewati entry
                should_breakeven = False
                if pos_type == "BUY" and current_price > op_price:
                    # Cek apakah profit sudah cukup jauh (minimal 30% dari jarak SL)
                    sl_dist = abs(op_price - current_sl)
                    profit_dist = current_price - op_price
                    if sl_dist > 0 and profit_dist >= sl_dist * 0.30:
                        should_breakeven = True
                elif pos_type == "SELL" and current_price < op_price:
                    sl_dist = abs(current_sl - op_price)
                    profit_dist = op_price - current_price
                    if sl_dist > 0 and profit_dist >= sl_dist * 0.30:
                        should_breakeven = True

                if should_breakeven:
                    log.info(f"🛡️ BREAKEVEN! {sym} {pos_type} → SL dipindah ke {op_price}")
                    try:
                        executor.modify_position(pos['id'], sl=op_price)
                        notifier.send_message(
                            f"🛡️ <b>BREAKEVEN AKTIF!</b>\n"
                            f"━━━━━━━━━━━━━━━\n"
                            f"💰 {pos_type} {sym}\n"
                            f"📍 Entry: {op_price} | Harga: {current_price}\n"
                            f"🛑 SL dipindah ke {op_price} (Risk-Free)"
                        )
                    except Exception as e:
                        log.error(f"Gagal set breakeven {sym}: {e}")
        except Exception as e:
            log.warning(f"Breakeven check error: {e}")


async def main():
    # Koneksi ke Telegram
    await client.start()
    me = await client.get_me()

    log.info("=" * 55)
    log.info(f"🤖 AUTO-TRADE SIGNAL BOT AKTIF (VPS Windows)")
    log.info(f"   Telegram : {me.first_name} (@{me.username})")
    log.info(f"   Mode     : {'🔸 DRY RUN (Tanpa Eksekusi)' if DRY_RUN else '🔴 LIVE TRADING'}")
    log.info(f"   Lot Size : {executor.lot_size} (60/40 split)")
    log.info(f"   Threshold: Dynamic (30% × SL distance)")
    log.info(f"   Strategy : Partial Close (60% TP1, 40% TP2 + Breakeven)")
    log.info(f"   Sumber   : {len(SOURCE_CHAT_IDS)} grup/channel")

    for src_id in SOURCE_CHAT_IDS:
        try:
            entity = await client.get_entity(src_id)
            name = getattr(entity, "title", str(src_id))
            log.info(f"     📡 {name} ({src_id})")
        except Exception:
            log.info(f"     📡 ID: {src_id}")

    # Koneksi ke MetaTrader 5 (synchronous)
    if not DRY_RUN:
        try:
            executor.connect()
            notifier.send_message(
                f"🤖 <b>Signal Bot AKTIF! (VPS Windows)</b>\n"
                f"Mode: LIVE | Lot: {executor.lot_size}\n"
                f"Channel: {len(SOURCE_CHAT_IDS)} sumber"
            )
        except Exception as e:
            log.error(f"❌ Gagal koneksi ke MT5: {e}")
            notifier.send_message(f"❌ Signal Bot gagal start: {e}")
            return

    log.info("=" * 55)
    log.info("⏳ Menunggu sinyal masuk... (Ctrl+C untuk berhenti)")

    # Jalankan background tasks
    loop = asyncio.get_event_loop()
    loop.create_task(health_check_loop())
    loop.create_task(breakeven_loop())  # Breakeven aktif (pindah SL ke entry saat profit >= 30% SL)

    await client.run_until_disconnected()

    # Cleanup
    if not DRY_RUN:
        executor.close()


if __name__ == "__main__":
    asyncio.run(main())
