"""
Rettangolo Runner V2 - LIVE_DEPLOY_V2 con migliorie diagnostiche (Mattia 04/08/2026)
====================================================================================
V2 del rettangolo_runner con:
- Path Linux corretti (no G:\\)
- Webhook su porta 5581
- REGIME DETECTOR check (blocca entries se BTC/ETH panic)
- TIME FILTER (blocca 04:00-14:59 Europe/Rome)
- ANTI-CLUSTER-LOSS check (pausa 4h dopo 2 SL consecutivi stesso symbol)
- SL Charter +30% (via sltp_engine_v2)

NON tocca live_deploy/ originale. NON interferisce col 1° account.
"""
import os
import sys
import time
import json
import requests
from datetime import datetime, timezone, time as dtime
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,
)
import regime_detector
import anti_cluster_loss

# === CONFIG V2 ===
LOOP_INTERVAL_SEC = 60
INTRADAY_LIMIT = 200
DAILY_LIMIT = 5
TIMEFRAME = "120"  # 2H

# V2: Webhook su porta 5581 (NON 5580)
WEBHOOK_URL = "http://127.0.0.1:5581/webhook"
WEBHOOK_SECRET = "TV_2026_MATTIA_DEMO"

# V2: Path Linux
SIGNAL_STATE_FILE = "/opt/charter-live/live_deploy_v2/logs/rettangolo_signal_state_v2.json"
LOG_FILE = "/opt/charter-live/live_deploy_v2/logs/rettangolo_runner_v2.log"

# Anti-doppia locale: cooldown 4h
SIGNAL_COOLDOWN_SEC = 14400

# === V2 MIGLIORIE: TIME FILTER ===
# Blocco entries 04:00-14:59 Europe/Rome (eccetto 15:00-16:59 = finestra d'oro).
# Lascia 15:00-20:00 operativa (fascia profittevole +196 USDT in 12gg).
# Lascia 02:00-03:00 operativa (marginale ma OK).
LOCAL_TZ = ZoneInfo("Europe/Rome")
BLOCK_HOURS_START = 4   # 04:00
BLOCK_HOURS_END = 14    # 14:59 (escluso)


def is_blocked_hour(now_local):
    """Ritorna True se l'orario attuale e' nella finestra bloccata 04:00-14:59."""
    h = now_local.hour
    return BLOCK_HOURS_START <= h <= BLOCK_HOURS_END


# === V2 MIGLIORIE: REGIME DETECTOR REFRESH INTERVAL ===
REGIME_CHECK_INTERVAL_SEC = 300  # controlla regime ogni 5 min (non serve ogni 60s)
_last_regime_check = 0


def ensure_regime_check():
    """Controlla regime_detector se e' passato abbastanza tempo. Cached."""
    global _last_regime_check
    now = time.time()
    if now - _last_regime_check > REGIME_CHECK_INTERVAL_SEC:
        try:
            regime_detector.check_and_update()
        except Exception as e:
            log(f"regime_detector.check_and_update err: {e}")
        _last_regime_check = now


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 _load_signal_state():
    try:
        if os.path.exists(SIGNAL_STATE_FILE):
            with open(SIGNAL_STATE_FILE, "r", encoding="utf-8") as f:
                return json.load(f)
    except Exception as e:
        log(f"  load_signal_state err: {e}")
    return {}


def _save_signal_state(state):
    try:
        os.makedirs(os.path.dirname(SIGNAL_STATE_FILE), exist_ok=True)
        tmp = SIGNAL_STATE_FILE + ".tmp"
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump(state, f, indent=2)
        os.replace(tmp, SIGNAL_STATE_FILE)
    except Exception as e:
        log(f"  save_signal_state err: {e}")


def _is_recent_signal(state, symbol, side, now_epoch):
    key = f"{symbol}_{side}"
    rec = state.get(key)
    if not rec:
        return False
    elapsed = now_epoch - float(rec.get("ts", 0))
    return elapsed < SIGNAL_COOLDOWN_SEC


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:
            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. V2: applica regime/time/anti-cluster check."""
    opened = 0
    signal_state = _load_signal_state()
    state_dirty = False
    now_epoch = time.time()
    now_local = datetime.now(timezone.utc).astimezone(LOCAL_TZ)

    # === V2 MIGLIORIA 1: TIME FILTER ===
    if is_blocked_hour(now_local):
        log(f"  TIME FILTER: blocco 04-14 attivo (now={now_local.strftime('%H:%M')}), skip ciclo intero")
        return 0

    # === V2 MIGLIORIA 2: REGIME DETECTOR (cached, refresh ogni 5min) ===
    ensure_regime_check()
    if regime_detector.is_panic():
        st = regime_detector.get_state()
        until = st.get("panic_until", 0)
        reason = st.get("panic_reason", "?")
        log(f"  REGIME PANIC: {reason} until {datetime.fromtimestamp(until, tz=timezone.utc).isoformat()}, skip ciclo intero")
        return 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

        # === V2 MIGLIORIA 3: ANTI-CLUSTER-LOSS (per symbol) ===
        if anti_cluster_loss.should_pause(sym):
            st = anti_cluster_loss.get_state()
            paused_until = st.get("symbols", {}).get(sym, {}).get("paused_until", 0)
            log(f"  ANTI-CLUSTER: {sym} paused until {datetime.fromtimestamp(paused_until, tz=timezone.utc).isoformat()}, skip")
            continue

        # 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]
        if len(intraday) < 3:
            log(f"  {sym}: klines insufficienti per valutare candela chiusa (intra={len(intraday)})")
            continue
        idx_closed = len(intraday) - 2
        pattern_mode = asset.get("pattern_mode", "relaxed")
        allow_martello_spinta = (pattern_mode == "relaxed")
        log(f"  {sym}: evaluating CLOSED candle idx={idx_closed} pattern_mode={pattern_mode}")
        sig = compute_signal(prev_daily, intraday, idx_closed,
                             sl_buffer_pct=0.01,
                             require_two_candles=True,
                             allow_martello_spinta=allow_martello_spinta)
        if sig is None:
            continue

        bybit_side = "Buy" if sig["signal"] == "LONG" else "Sell"
        log(f"  {sym}: SEGNALE {sig['signal']} entry={sig['entry']:.4f} sl={sig['sl']:.4f} tp={sig['tp']:.4f}")

        # 2. ANTI-DOPPIA LOCALE (4h)
        if _is_recent_signal(signal_state, sym, bybit_side, now_epoch):
            key = f"{sym}_{bybit_side}"
            elapsed_h = (now_epoch - float(signal_state[key].get("ts", 0))) / 3600
            log(f"    ANTI-DOPPIA LOCALE: {sym} {bybit_side} ultimo segnale {elapsed_h:.1f}h fa, skip")
            continue

        # 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. current price
        current_price = get_ticker_price(client, sym)
        if current_price <= 0:
            log(f"    ticker fail per {sym}, skip")
            continue
        log(f"    current_price: {current_price} (log only)")

        # 7. apri posizione VIA WEBHOOK V2 (porta 5581)
        try:
            payload = {
                "secret": WEBHOOK_SECRET,
                "symbol": sym,
                "side": bybit_side,
                "qty": 1,
                "price": float(sig["entry"]),
                "strategy": "rettangolo_v2",
                "leverage": LEVERAGE,
                "timeframe": asset.get("timeframe", "120"),
                "comment": f"rettangolo V2 auto {sig['signal']}",
                "sl_price": sig.get("sl"),
                "tp_price": sig.get("tp"),
            }
            r = requests.post(WEBHOOK_URL, json=payload, timeout=10)
            try:
                result = r.json()
            except Exception:
                result = {}
            if not (r.status_code == 200 and result.get("ok")):
                err_msg = (result.get("error") or r.text or "")[:200]
                log(f"    WEBHOOK V2 FAIL: status={r.status_code} err={err_msg}, skip")
                continue
            req_id = (result.get("request_id") or "?")[:8]
            log(f"    WEBHOOK V2 OK: request_id={req_id}... queued for {bybit_side} {sym}")

            # Polling per conferma fill
            confirmed = False
            status_url = f"{WEBHOOK_URL.replace('/webhook', '')}/webhook/status/{req_id}"
            for attempt in range(15):
                time.sleep(1)
                try:
                    sr = requests.get(status_url, timeout=5)
                    sresult = sr.json() if sr.status_code == 200 else {}
                except Exception:
                    sresult = {}
                st = sresult.get("status")
                if st == "completed":
                    order_id = sresult.get("order_id", "?")
                    log(f"    WEBHOOK V2 FILL CONFERMATO: orderId={order_id} after {attempt+1}s")
                    confirmed = True
                    break
                if st == "failed":
                    err = sresult.get("error", "")[:200]
                    log(f"    WEBHOOK V2 FAILED dopo {attempt+1}s: {err}, skip")
                    confirmed = False
                    break
            if not confirmed:
                log(f"    WEBHOOK V2 TIMEOUT: req_id={req_id}, NON segno anti-doppia")
                continue

            signal_state[f"{sym}_{bybit_side}"] = {
                "side": bybit_side,
                "ts": now_epoch,
                "entry": float(sig["entry"]),
                "request_id": req_id,
            }
            state_dirty = True
            opened += 1
        except Exception as e:
            log(f"    webhook v2 exception: {e}, skip")
            continue

    if state_dirty:
        _save_signal_state(signal_state)
        log(f"  signal_state saved: {len(signal_state)} entries")

    return opened


def main_loop():
    client = BybitDemoClient()
    log(f"=== RETTANGOLO RUNNER V2 AVVIATO (loop {LOOP_INTERVAL_SEC}s, ANTI_DUP_COOLDOWN={SIGNAL_COOLDOWN_SEC}s, TIME_BLOCK={BLOCK_HOURS_START}-{BLOCK_HOURS_END}, REGIME_PANIC=auto, ANTI_CLUSTER_LOSS=auto) ===")
    while True:
        try:
            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 V2 --once ({len(assets)} asset) ===")
        run_once(client, assets)
    else:
        main_loop()
