# -*- coding: utf-8 -*-
"""SWARM v5 -- Lightweight Autonomous MT5 Desktop Connector.

Connects to local MetaTrader 5 terminal, extracts execution telemetry,
and streams heartbeats & closed trades to SWARM Cloud Orchestrator.

SECURITY & PRIVACY GUARANTEE:
- Requires ONLY 1 input: Your SWARM API Key.
- NEVER reads, stores, or transmits your MT5 broker passwords.
- Stores offline events in local SQLite buffer with exponential backoff.
"""

import os
import sys
import time
import json
import sqlite3
import logging
import urllib.request
import urllib.error
from datetime import datetime, timezone

try:
    import MetaTrader5 as mt5
except ImportError:
    mt5 = None

SERVER_URL = os.environ.get("SWARM_SERVER_URL", "https://101.32.244.251.nip.io")
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector_config.json")
BUFFER_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "connector_buffer.db")

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S"
)
logger = logging.getLogger("SwarmConnector")


class LocalBuffer:
    """Stores outgoing payloads locally when internet is offline."""
    def __init__(self, db_path=BUFFER_DB):
        self.db_path = db_path
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("""
            CREATE TABLE IF NOT EXISTS buffer (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                endpoint TEXT NOT NULL,
                payload TEXT NOT NULL,
                created_at TEXT NOT NULL
            )
            """)
            conn.commit()

    def push(self, endpoint: str, payload: dict):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute(
                "INSERT INTO buffer (endpoint, payload, created_at) VALUES (?, ?, ?)",
                (endpoint, json.dumps(payload), datetime.now(timezone.utc).isoformat())
            )
            conn.commit()

    def get_pending(self, limit=20):
        with sqlite3.connect(self.db_path) as conn:
            cursor = conn.execute("SELECT id, endpoint, payload FROM buffer ORDER BY id ASC LIMIT ?", (limit,))
            return cursor.fetchall()

    def remove(self, item_id: int):
        with sqlite3.connect(self.db_path) as conn:
            conn.execute("DELETE FROM buffer WHERE id = ?", (item_id,))
            conn.commit()


def load_or_prompt_api_key() -> str:
    """Load API Key from config file, environment, or prompt user."""
    env_key = os.environ.get("SWARM_API_KEY")
    if env_key:
        return env_key.strip()

    if os.path.exists(CONFIG_FILE):
        try:
            with open(CONFIG_FILE, "r", encoding="utf-8") as f:
                cfg = json.load(f)
                if cfg.get("api_key"):
                    return cfg["api_key"].strip()
        except Exception:
            pass

    print("=" * 60)
    print("  SWARM v5 -- DESKTOP CONNECTOR SETUP")
    print("  Privasi: Kami TIDAK PERNAH meminta password MT5 Anda.")
    print("=" * 60)
    key = input("Masukkan SWARM API Key Anda (dari menu Settings di dashboard): ").strip()
    if not key:
        print("Error: API Key tidak boleh kosong.")
        sys.exit(1)

    try:
        with open(CONFIG_FILE, "w", encoding="utf-8") as f:
            json.dump({"api_key": key, "server_url": SERVER_URL}, f, indent=2)
        print(f"Konfigurasi tersimpan di {CONFIG_FILE}
")
    except Exception as e:
        logger.warning(f"Gagal menyimpan config file: {e}")

    return key


def send_request(endpoint: str, payload: dict, api_key: str, server_url: str = SERVER_URL) -> tuple[bool, int, dict]:
    """Send JSON payload to SWARM server with Bearer auth."""
    url = f"{server_url.rstrip('/')}{endpoint}"
    data = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(url, data=data, headers={
        "Content-Type": "application/json",
        "Authorization": f"Bearer {api_key}",
        "User-Agent": "SwarmConnector/5.0"
    })
    try:
        with urllib.request.urlopen(req, timeout=10) as resp:
            body = resp.read().decode("utf-8")
            return True, resp.status, json.loads(body) if body else {}
    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8")
        try:
            err_json = json.loads(body)
        except Exception:
            err_json = {"error": body}
        return False, e.code, err_json
    except Exception as e:
        return False, 0, {"error": str(e)}


def run_connector():
    """Main connector loop."""
    print("""
  ____ __          __     _____  __  __ 
 / ___|\ \        / /\   |  _ \|  \/  |
 \___ \ \ \  /\  / /  \  | |_) | |\/| |
  ___) | \ \/  \/ / /\ \ |  _ <| |  | |
 |____/   \__/\__/_/  \_\|_| \_\_|  |_|
   AUTONOMOUS ORCHESTRATOR CONNECTOR
    """)
    api_key = load_or_prompt_api_key()
    buffer = LocalBuffer()

    logger.info(f"Menghubungkan ke SWARM Server di {SERVER_URL}...")
    if not mt5:
        logger.error("Modul MetaTrader5 Python tidak ditemukan. Jalankan: pip install MetaTrader5")
        sys.exit(1)

    if not mt5.initialize():
        logger.error(f"Gagal menghubungkan ke terminal MT5: {mt5.last_error()}")
        logger.error("Pastikan terminal MetaTrader 5 sudah terbuka dan login ke akun trading Anda.")
        sys.exit(1)

    acc = mt5.account_info()
    if not acc:
        logger.error("Gagal membaca info akun MT5. Pastikan MT5 telah login.")
        sys.exit(1)

    is_demo = (acc.trade_mode == mt5.ACCOUNT_TRADE_MODE_DEMO)
    broker_name = acc.company or "MetaQuotes"
    server_name = acc.server or "MT5 Server"
    masked_login = f"****{str(acc.login)[-4:]}" if len(str(acc.login)) >= 4 else "****"

    logger.info("=" * 50)
    logger.info(f"MT5 Terhubung: {broker_name} ({'DEMO' if is_demo else 'REAL'})")
    logger.info(f"Akun Terdeteksi: {masked_login} (Password tetap aman di terminal Anda)")
    logger.info("Streaming telemetry aktif...")
    logger.info("=" * 50)

    last_heartbeat = 0
    backoff_delay = 1

    while True:
        try:
            now = time.time()

            # Flush local buffer if any pending items
            pending = buffer.get_pending(limit=10)
            if pending:
                logger.info(f"Mengirim {len(pending)} data offline dari buffer...")
                for item_id, ep, pl_str in pending:
                    pl = json.loads(pl_str)
                    ok, code, res = send_request(ep, pl, api_key)
                    if ok:
                        buffer.remove(item_id)
                        backoff_delay = 1
                    else:
                        break

            # Send Heartbeat & Positions every 30 seconds
            if now - last_heartbeat >= 30:
                acc_live = mt5.account_info()
                hb_payload = {
                    "broker_name": broker_name,
                    "is_demo": is_demo,
                    "server_hint": server_name
                }
                ok, status_code, resp = send_request("/api/ingest/heartbeat", hb_payload, api_key)

                if ok:
                    logger.info(f"Heartbeat terkirim [HTTP {status_code}]. Server tersinkronisasi.")
                    backoff_delay = 1

                    # Send Open Positions Snapshot
                    positions = mt5.positions_get()
                    pos_list = []
                    if positions:
                        for p in positions:
                            pos_list.append({
                                "ticket": p.ticket,
                                "symbol": p.symbol,
                                "type": "BUY" if p.type == mt5.ORDER_TYPE_BUY else "SELL",
                                "volume": p.volume,
                                "price_open": p.price_open,
                                "price_current": p.price_current,
                                "profit": p.profit
                            })
                    send_request("/api/ingest/positions", {"positions": pos_list}, api_key)
                    last_heartbeat = now
                else:
                    if status_code == 401:
                        logger.critical("KONEKSI DITOLAK: API Key tidak valid atau telah dicabut oleh pengguna!")
                        time.sleep(10)
                    else:
                        logger.warning(f"Koneksi gagal [HTTP {status_code}], menyimpan ke buffer lokal...")
                        buffer.push("/api/ingest/heartbeat", hb_payload)
                        time.sleep(min(backoff_delay, 30))
                        backoff_delay *= 2

            time.sleep(1)

        except KeyboardInterrupt:
            logger.info("Connector dihentikan oleh pengguna.")
            break
        except Exception as e:
            logger.error(f"Terjadi kesalahan pada loop konektor: {e}")
            time.sleep(5)

    mt5.shutdown()


if __name__ == "__main__":
    run_connector()
