"""
Rettangolo Runner - Esecuzione live della strategia RETTANGOLO.

Loop ogni 60s:
  1. Per ogni symbol in rettangolo_assets.csv (strategy=rettangolo)
  2. Calcola compute_signal() (rettangolo_strategy.py)
  3. Se c'è segnale LONG o SHORT:
     - Safety cap MAX_OPEN_POSITIONS (10 globale, già in rettangolo_config)
     - Se gia' posizione aperta su quel symbol: skip
     - Altrimenti: set leverage 3x, market order, set SL/TP via set_trading_stop
  4. Logga tutto in rettangolo_runner.log

NON tocca file di config (rettangolo_assets.csv, rettangolo_config.py).
NON modifica posizioni esistenti (time-stop lo fa Pine via webhook).
"""
import os
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from zoneinfo import ZoneInfo

sys.path.insert(0, str(Path(__file__).parent))

from bybit_demo_client import BybitDemoClient
from rettangolo_strategy import compute_signal
from rettangolo_config import (
    load_assets, ORDER_VALUE_USD, LEVERAGE, MAX_OPEN_POSITIONS, max_qty_for_order_value,
)

# === CONFIG ===
LOOP_INTERVAL_SEC = 60
INTRADAY_LIMIT = 200
DAILY_LIMIT = 5
TIMEFRAME = "120"  # 2H (hardcoded, matches rettangolo_assets.csv)

# Path monitor per leggere l'ultimo range daily visualizzato (no, lo calcoliamo noi)
LOG_FILE = r"G:\AI TRADING ENGINE\live_deploy\webhook_listener\logs\rettangolo_runner.log"
LOCAL_TZ = ZoneInfo("Europe/Rome")


def log(msg):
    ts = datetime.now(timezone.utc).astimezone(LOCAL_TZ).isoformat()
    line = f"[{ts}] {msg}"
    print(line, flush=True)
    try:
        os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
        with open(LOG_FILE, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except Exception:
        pass


def fetch_klines(client, symbol, interval, limit):
    raw = client.fetch_ohlcv(symbol, interval, limit)
    out = []
    for row in raw:
        ts_ms, o, h, l, c = int(row[0]), row[1], row[2], row[3], row[4]
        out.append({
            "ts": ts_ms,
            "date": datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc).astimezone(LOCAL_TZ).isoformat(),
            "open": o, "high": h, "low": l, "close": c,
        })
    return out


def set_leverage_safe(client, symbol, leverage=3):
    try:
        r = client.set_leverage(symbol, leverage)
        return True, f"retCode={r.get('retCode')}"
    except Exception as e:
        msg = str(e)
        if "110043" in msg:  # leverage already at target
            return True, "leverage already at target (skip)"
        return False, f"err: {e}"


def count_open_positions(client):
    try:
        positions = client.fetch_positions()
        return sum(1 for p in positions if float(p.get("size", 0) or 0) > 0)
    except Exception:
        return -1


def get_ticker_price(client, symbol):
    try:
        d = client._request("GET", "/v5/market/tickers",
                             {"category": "linear", "symbol": symbol}, signed=False)
        lst = d.get("result", {}).get("list", [])
        if lst:
            return float(lst[0].get("lastPrice", 0) or 0)
    except Exception:
        pass
    return 0.0


def run_once(client, rettangolo_assets):
    """Processa tutti gli asset rettangolo. Ritorna numero di trade aperti in questo giro."""
    opened = 0
    for asset in rettangolo_assets:
        sym = asset["symbol"]
        tf_min = asset.get("tf_min", "120")
        try:
            tf_int = int(tf_min)
        except (TypeError, ValueError):
            tf_int = 120

        # 1. fetch candele
        try:
            daily = fetch_klines(client, sym, "D", DAILY_LIMIT)
            intraday = fetch_klines(client, sym, str(tf_int), INTRADAY_LIMIT)
        except Exception as e:
            log(f"  {sym}: fetch klines err: {e}")
            continue

        if len(daily) < 2 or len(intraday) < 2:
            log(f"  {sym}: klines insufficienti (daily={len(daily)}, intra={len(intraday)})")
            continue

        prev_daily = daily[-2]

        # P010 Charter fix 2026-07-18: valuta SEMPRE candela chiusa [-2], MAI [-1] (in formazione).
        # Bybit API restituisce candele fino a quella in formazione; intraday[-1] e' viva, intraday[-2] e' chiusa.
        if len(intraday) < 3:
            log(f"  {sym}: klines insufficienti per valutare candela chiusa (intra={len(intraday)})")
            continue
        idx_closed = len(intraday) - 2
        log(f"  {sym}: evaluating CLOSED candle idx={idx_closed} ts={intraday[idx_closed].get('time', '?')} (current live candle skipped)")
        sig = compute_signal(prev_daily, intraday, idx_closed)
        if sig is None:
            continue  # niente segnale, silenzio

        log(f"  {sym}: SEGNALE {sig['signal']} entry={sig['entry']:.4f} sl={sig['sl']:.4f} tp={sig['tp']:.4f}")

        # 3. safety cap
        n_open = count_open_positions(client)
        if n_open < 0:
            log(f"    err count positions, skip {sym}")
            continue
        if n_open >= MAX_OPEN_POSITIONS:
            log(f"    safety cap {n_open}/{MAX_OPEN_POSITIONS}, skip {sym}")
            continue

        # 4. verifica posizione gia' aperta su questo symbol
        try:
            existing = client.fetch_positions(sym)
            if existing:
                log(f"    posizione gia' aperta su {sym} ({existing[0]['side']} size={existing[0]['size']}), skip")
                continue
        except Exception as e:
            log(f"    err fetch position {sym}: {e}, skip")
            continue

        # 5. set leverage 3x
        ok, msg = set_leverage_safe(client, sym, LEVERAGE)
        if not ok:
            log(f"    set_leverage FAIL: {msg}, skip")
            continue
        log(f"    leverage: {msg}")

        # 6. calcola qty in base a MANIFESTO (nozionale 500 USDT) e current price
        # Per SL safe di Bybit: se SL troppo vicino al current (<0.5%),
        # Bybit rifiuta. compute_signal restituisce sl gia' calcolato.
        current_price = get_ticker_price(client, sym)
        if current_price <= 0:
            log(f"    ticker fail per {sym}, skip")
            continue
        max_qty = max_qty_for_order_value(current_price)
        # arrotonda a 3 decimali (compatibilita' Bybit)
        qty = round(max_qty, 3)
        if qty <= 0:
            log(f"    qty {qty} <= 0 per {sym} @ {current_price}, skip")
            continue
        log(f"    qty: {qty} (nozionale {qty * current_price:.2f} USDT)")

        # 7. apri posizione
        bybit_side = "Buy" if sig["signal"] == "LONG" else "Sell"
        try:
            r = client.create_market_order(sym, bybit_side, qty)
            if r.get("retCode") != 0:
                log(f"    create_market_order FAIL: {r.get('retMsg')} (code {r.get('retCode')}), skip")
                continue
            oid = r.get("result", {}).get("orderId")
            log(f"    ORDER APERTA: orderId={oid} {bybit_side} {qty} {sym} @ ~{current_price}")
            opened += 1
        except Exception as e:
            log(f"    create_market_order exception: {e}, skip")
            continue

        # 8. set SL/TP
        try:
            sltp = client.set_trading_stop(sym, sl_price=sig["sl"], tp_price=sig["tp"])
            if sltp.get("retCode") == 0:
                log(f"    SLTP SETTATO: sl={sig['sl']:.4f} tp={sig['tp']:.4f}")
            else:
                log(f"    SLTP FAIL: {sltp.get('retMsg')} (code {sltp.get('retCode')})")
        except Exception as e:
            log(f"    set_trading_stop exception: {e}")

    return opened


def main_loop():
    client = BybitDemoClient()
    log(f"=== RETTANGOLO RUNNER AVVIATO (loop {LOOP_INTERVAL_SEC}s, MANIFESTO nozionale={ORDER_VALUE_USD}, leva={LEVERAGE}, cap={MAX_OPEN_POSITIONS}) ===")
    while True:
        try:
            # Ricarica asset list da CSV ad ogni iterazione (modifiche riflesse in 60s)
            assets = [a for a in load_assets() if a.get("strategy", "rettangolo") == "rettangolo" and a.get("enabled", True)]
            if not assets:
                log("nessun asset rettangolo enabled nel CSV, skip")
            else:
                log(f"processo {len(assets)} asset rettangolo: {','.join(a['symbol'] for a in assets)}")
                run_once(client, assets)
        except Exception as e:
            log(f"loop exception: {e}")
        time.sleep(LOOP_INTERVAL_SEC)


if __name__ == "__main__":
    if "--once" in sys.argv:
        client = BybitDemoClient()
        assets = [a for a in load_assets() if a.get("strategy", "rettangolo") == "rettangolo" and a.get("enabled", True)]
        log(f"=== RETTANGOLO RUNNER --once ({len(assets)} asset) ===")
        run_once(client, assets)
    else:
        main_loop()
