"""
Dashboard Flask live multi-asset - AI Trading Engine (v3 lightweight)
Server leggero: candele live da Bybit (ZEC + AERO + DASH + SOL) + segnali + equity + posizioni.
5 route:
  /         → vista riassuntiva (grid 2x2 con tutti i 4 setup)
  /zec      → full-screen ZEC 4H
  /aero     → full-screen AERO 4H
  /dash     → full-screen DASH 4H
  /sol      → full-screen SOL 4H
  /api/data → JSON aggregato

Nessun grafico pesante (no Plotly/Chart.js). Solo tabelle + candele ASCII-style.
Read-only, no auth richiesta.
"""
import os
import json
from pathlib import Path
from datetime import datetime, timezone
import ccxt
import pandas as pd
import numpy as np
from flask import Flask, jsonify, render_template_string, make_response

ENV_FILE = Path(r"G:\AI TRADING ENGINE\Bybit_new.env")
STATE_DIR = Path(__file__).parent

SETUPS = [
    {"id": "zec",  "name": "ZEC_4H",          "symbol": "ZEC/USDT:USDT",  "tf": "4h", "mask": "10001011",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 500,
     "validation": "🟢 TRIPLA VALIDATA"},
    {"id": "aero", "name": "AERO_4H",         "symbol": "AERO/USDT:USDT", "tf": "4h", "mask": "01010101",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 250,
     "validation": "🟡 2/3 BORDERLINE"},
    {"id": "dash", "name": "DASH_4H",         "symbol": "DASH/USDT:USDT", "tf": "4h", "mask": "10000111",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 250,
     "validation": "🟡 2/3 BORDERLINE"},
    {"id": "pyth_dls",  "name": "PYTH_3H_CLL_CLS_CSL_DLS", "symbol": "PYTH/USDT:USDT", "tf": "3h", "mask": "00100111",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 250,
     "validation": "🆕 PYTH 00100111"},
    {"id": "pendle_cdl",  "name": "PENDLE_4H_CSL_DLL_DLS_DSL","symbol": "PENDLE/USDT:USDT", "tf": "4h", "mask": "00101110",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 250,
     "validation": "🆕 PENDLE 00101110"},
    {"id": "sol",  "name": "SOL_4H_NAIVE",    "symbol": "SOL/USDT:USDT",  "tf": "4h", "mask": "00101000",
     "bb_length": 55, "bb_mult": 1.0, "roc_length": 36, "atr_length": 16, "margin": 0,
     "validation": "📡 NAIVE ALERT (no Charter)"},
]

app = Flask(__name__)

_cache = {}
_cache_ttl = 60  # secondi


def get_client():
    return ccxt.bybit({"enableRateLimit": True, "options": {"defaultType": "linear"}})


def compute_indicators(df, bb_len, factor, roc_len, atr_len):
    df = df.copy()
    df.columns = [c.strip().lower() for c in df.columns]
    close = df["close"].astype(float); high = df["high"].astype(float); low = df["low"].astype(float)
    vol = df["volume"].astype(float)
    sma = close.rolling(bb_len, min_periods=bb_len).mean()
    sd = close.rolling(bb_len, min_periods=bb_len).std(ddof=0)
    df["bb_plus"] = sma + factor * sd
    df["bb_minus"] = sma - factor * sd
    prev = close.shift(roc_len)
    df["roc"] = (close - prev) / prev * 100.0
    tr = pd.concat([high - low, (high - low.shift(1)).abs(), (low - high.shift(1)).abs()], axis=1).max(axis=1)
    df["atr"] = tr.ewm(alpha=1.0/atr_len, adjust=False).mean()
    sp = np.where(close > df["open"].values, 1, np.where(close < df["open"].values, -1, 0))
    sv = np.where(vol > np.roll(vol.values, 1), 1, np.where(vol < np.roll(vol.values, 1), -1, 0))
    sr = np.where(df["roc"].values > 0, 1, np.where(df["roc"].values < 0, -1, 0))
    cd_idx = np.full(len(df), -1, dtype=np.int8)
    valid = (sp != 0) & (sv != 0) & (sr != 0) & ~np.isnan(df["roc"].values) & ~np.isnan(df["bb_plus"].values)
    m1 = valid & (sp == 1) & (sv == 1); cd_idx[m1 & (sr > 0)] = 0; cd_idx[m1 & (sr < 0)] = 1
    m2 = valid & (sp == -1) & (sv == -1); cd_idx[m2 & (sr > 0)] = 2; cd_idx[m2 & (sr < 0)] = 3
    m3 = valid & (sp == 1) & (sv == -1); cd_idx[m3 & (sr > 0)] = 4; cd_idx[m3 & (sr < 0)] = 5
    m4 = valid & (sp == -1) & (sv == 1); cd_idx[m4 & (sr > 0)] = 6; cd_idx[m4 & (sr < 0)] = 7
    df["cd_idx"] = cd_idx
    return df


def fetch_setup_cached(ex, setup):
    key = setup["id"]
    now = datetime.now().timestamp()
    if key in _cache and (now - _cache[key]["ts"]) < _cache_ttl:
        return _cache[key]["data"]
    ohlcv = ex.fetch_ohlcv(setup["symbol"], setup["tf"], limit=200)
    df = pd.DataFrame(ohlcv, columns=["timestamp", "open", "high", "low", "close", "volume"])
    df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
    df = compute_indicators(df, setup["bb_length"], setup["bb_mult"], setup["roc_length"], setup["atr_length"])
    _cache[key] = {"ts": now, "data": df}
    return df


def detect_signal(df, mask_str):
    if len(df) < 100: return None
    last = df.iloc[-1]
    if pd.isna(last["bb_plus"]) or pd.isna(last["roc"]): return None
    mask_padded = mask_str.zfill(8)
    cd_active = [int(c) for c in mask_padded]
    cd = int(last["cd_idx"])
    if cd < 0 or not cd_active[cd]: return None
    close = float(last["close"]); atr = float(last["atr"])
    if close > float(last["bb_plus"]) and last["roc"] > 0: return "LONG"
    if close < float(last["bb_minus"]) and last["roc"] < 0: return "SHORT"
    return None


def make_sparkline(values, width=20, height=20):
    """Crea sparkline ASCII-style (no JS, no charts)."""
    if len(values) < 2: return "..."
    mn, mx = min(values), max(values)
    if mx == mn: return "─" * width
    bars = "▁▂▃▄▅▆▇█"
    step = (mx - mn) / (len(bars) - 1)
    line = ""
    for v in values[-width:]:
        idx = min(int((v - mn) / step), len(bars) - 1)
        line += bars[idx]
    return line


def fetch_setup_full(ex, setup):
    try:
        df = fetch_setup_cached(ex, setup)
        sig = detect_signal(df, setup["mask"])
        last = df.iloc[-1]
        # Stats
        df_30 = df.tail(180)
        n = len(df_30)
        vol_30 = df_30["close"].pct_change().std() * np.sqrt(365 * 6) * 100
        high_30 = df_30["high"].max()
        low_30 = df_30["low"].min()
        range_30_pct = (high_30 - low_30) / low_30 * 100
        # Candele ultime 12 per tabella
        candles = []
        for _, row in df.tail(12).iterrows():
            cd = int(row["cd_idx"])
            cd_name = ["CLL", "CLS", "CSL", "CSS", "DLL", "DLS", "DSL", "DSS"][cd] if cd >= 0 else "N/A"
            candles.append({
                "ts": row["timestamp"].strftime("%d/%m %H:%M"),
                "close": float(row["close"]),
                "bb_plus": float(row["bb_plus"]) if not pd.isna(row["bb_plus"]) else 0,
                "bb_minus": float(row["bb_minus"]) if not pd.isna(row["bb_minus"]) else 0,
                "roc": float(row["roc"]) if not pd.isna(row["roc"]) else 0,
                "atr": float(row["atr"]) if not pd.isna(row["atr"]) else 0,
                "cd_name": cd_name,
            })
        # Sparkline close
        sparkline = make_sparkline(df["close"].tail(50).tolist(), width=30)
        return {
            "id": setup["id"], "name": setup["name"], "symbol": setup["symbol"], "tf": setup["tf"],
            "mask": setup["mask"], "validation": setup["validation"],
            "last_close": float(last["close"]),
            "last_ts": last["timestamp"].isoformat(),
            "atr": float(last["atr"]) if not pd.isna(last["atr"]) else 0,
            "bb_plus": float(last["bb_plus"]) if not pd.isna(last["bb_plus"]) else 0,
            "bb_minus": float(last["bb_minus"]) if not pd.isna(last["bb_minus"]) else 0,
            "roc": float(last["roc"]) if not pd.isna(last["roc"]) else 0,
            "signal": sig,
            "n_candles": int(n),
            "vol_30d_pct": round(vol_30, 2),
            "high_30d": float(high_30),
            "low_30d": float(low_30),
            "range_30d_pct": round(range_30_pct, 2),
            "sparkline": sparkline,
            "candles": candles,
        }
    except Exception as e:
        return {"id": setup["id"], "name": setup["name"], "error": str(e)}


DASHBOARD_HTML = """
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<title>AI Trading Engine — Live Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
  :root { --bg: #0f1117; --surface: #1a1d27; --border: #2a2f3d; --text: #e8eaf0; --muted: #7c8294;
          --green: #10b981; --red: #f43f5e; --amber: #f59e0b; }
  * { box-sizing: border-box; margin: 0; padding: 0; }
  body { font-family: 'Outfit', sans-serif; background: var(--bg); color: var(--text); padding: 24px; }
  .container { max-width: 1600px; margin: 0 auto; }
  .header { display: flex; justify-content: space-between; align-items: center; padding-bottom: 16px; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
  h1 { font-size: 22px; font-weight: 600; }
  .badge { padding: 4px 10px; border-radius: 999px; font-size: 11px; font-weight: 600; text-transform: uppercase; background: rgba(16,185,129,0.15); color: var(--green); }
  .pulse { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; margin-right: 6px; vertical-align: middle; }
  @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
  .nav-bar { display: flex; gap: 8px; margin-bottom: 16px; }
  .nav-link { padding: 6px 12px; border-radius: 6px; background: var(--surface); color: var(--text); text-decoration: none; font-size: 12px; font-weight: 500; border: 1px solid var(--border); }
  .nav-link:hover { border-color: var(--green); }
  .nav-link.all { background: rgba(59,130,246,0.15); color: #60a5fa; border-color: #60a5fa; }
  .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
  @media (max-width: 1280px) { .grid { grid-template-columns: 1fr; } }
  .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 16px; }
  .card-single { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 20px; max-width: 1500px; margin: 0 auto; }
  .setup-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
  .setup-name { font-size: 16px; font-weight: 600; }
  .setup-validation { font-size: 11px; padding: 3px 8px; border-radius: 6px; background: rgba(16,185,129,0.15); color: var(--green); }
  .v-amber { background: rgba(245,158,11,0.15); color: var(--amber); }
  .v-blue { background: rgba(59,130,246,0.15); color: #60a5fa; }
  .stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 10px; }
  .stat { background: rgba(255,255,255,0.02); padding: 8px; border-radius: 6px; }
  .stat-label { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 3px; }
  .stat-value { font-size: 14px; font-weight: 600; font-family: 'JetBrains Mono', monospace; }
  .signal-long { color: var(--green); font-weight: 700; }
  .signal-short { color: var(--red); font-weight: 700; }
  .signal-none { color: var(--muted); }
  .spark { font-family: 'JetBrains Mono', monospace; font-size: 14px; color: var(--green); letter-spacing: -1px; padding: 4px 0; }
  .spark.neg { color: var(--red); }
  table { width: 100%; border-collapse: collapse; font-family: 'JetBrains Mono', monospace; font-size: 11px; margin-top: 8px; }
  th { text-align: left; color: var(--muted); font-weight: 500; padding: 4px 6px; border-bottom: 1px solid var(--border); font-size: 10px; }
  td { padding: 6px; border-bottom: 1px solid rgba(255,255,255,0.04); }
  .footer { margin-top: 32px; padding-top: 16px; border-top: 1px solid var(--border); font-size: 11px; color: var(--muted); font-family: 'JetBrains Mono', monospace; }
</style>
</head>
<body>
<div class="container">
  <div class="header">
    <h1><span class="pulse"></span>AI Trading Engine — Live Dashboard</h1>
    <div><span class="badge">LIVE Bybit Demo</span></div>
  </div>

  <div class="nav-bar">
    <a href="/" class="nav-link all" id="nav-all">📊 ALL (5 setup)</a>
    <a href="/zec" class="nav-link" id="nav-zec">🟢 ZEC 4H</a>
    <a href="/aero" class="nav-link" id="nav-aero">🟡 AERO 4H</a>
    <a href="/dash" class="nav-link" id="nav-dash">🟡 DASH 4H</a>
    <a href="/pyth_dls" class="nav-link" id="nav-pyth_dls">🆕 PYTH 3H 00100111</a>
    <a href="/pendle_cdl" class="nav-link" id="nav-pendle_cdl">🆕 PENDLE 4H CSL+DLL+DLS+DSL</a>
    <a href="/sol" class="nav-link" id="nav-sol">📡 SOL 4H</a>
  </div>

  <div id="content">
    <p style="color: var(--muted); text-align: center; padding: 40px;">⏳ Caricamento da Bybit...</p>
  </div>

  <div class="footer">
    <span id="last_update">--</span> · Bybit Demo (api-demo.bybit.com) · Read-only · Charter: TP1+3% / TP2+5% / SL ATR 2x · Live bot Charter attivo · Refresh ogni 60s
  </div>
</div>

<script>
const ACTIVE_FILTER = "__ACTIVE_FILTER__";

async function refresh() {
  const content = document.getElementById('content');
  const url = ACTIVE_FILTER ? '/api/' + ACTIVE_FILTER : '/api/all';
  try {
    const r = await fetch(url);
    const d = await r.json();
    const setups = d.setups || [];
    if (setups.length === 0) {
      content.innerHTML = '<div class="card">⚠️ Nessun setup</div>';
      return;
    }
    const isSingle = setups.length === 1;
    content.innerHTML = setups.map(s => renderSetup(s, isSingle)).join('');
    document.getElementById('last_update').textContent = 'Last update: ' + new Date().toLocaleString('it-IT');
  } catch (e) { console.error(e); content.innerHTML = '<div class="card">⚠️ Errore: ' + e.message + '</div>'; }
}

function renderSetup(s, single) {
  if (s.error) return '<div class="card"><h3>' + s.name + '</h3><p style="color:#f43f5e">' + s.error + '</p></div>';
  const sigHtml = s.signal === 'LONG' ? '<span class="signal-long">▲ LONG</span>'
                : s.signal === 'SHORT' ? '<span class="signal-short">▼ SHORT</span>'
                : '<span class="signal-none">IDLE</span>';
  let valClass = 'v-amber';
  if (s.validation.includes('TRIPLA')) valClass = 'v-green';
  else if (s.validation.includes('NAIVE') || s.validation.includes('ALERT')) valClass = 'v-blue';
  const cardClass = single ? 'card-single' : 'card';
  // Sparkline: verde se close finale > prima, rosso altrimenti
  const sparkClass = s.sparkline && s.sparkline.length > 5 ? (s.last_close > (s.low_30d + s.high_30d) / 2 ? 'spark' : 'spark neg') : 'spark';
  return `<div class="${cardClass}">
    <div class="setup-header">
      <div>
        <div class="setup-name">${s.name} · ${s.symbol} ${s.tf}</div>
        <div style="font-size:10px;color:var(--muted);font-family:'JetBrains Mono',monospace;margin-top:2px">mask=${s.mask} · ${s.n_candles} candles</div>
      </div>
      <div style="display:flex;gap:8px;align-items:center">
        ${sigHtml}
        <span class="setup-validation ${valClass}">${s.validation}</span>
      </div>
    </div>
    <div class="${sparkClass}" style="padding:6px 0">${s.sparkline || ''}</div>
    <div class="stats-row">
      <div class="stat"><div class="stat-label">Prezzo</div><div class="stat-value">$${s.last_close.toFixed(4)}</div></div>
      <div class="stat"><div class="stat-label">BB+ / BB-</div><div class="stat-value">${s.bb_plus.toFixed(2)} / ${s.bb_minus.toFixed(2)}</div></div>
      <div class="stat"><div class="stat-label">ROC / ATR</div><div class="stat-value">${s.roc.toFixed(2)}% / ${s.atr.toFixed(4)}</div></div>
      <div class="stat"><div class="stat-label">Vol 30gg</div><div class="stat-value">${s.vol_30d_pct.toFixed(1)}%</div></div>
    </div>
    <div class="stats-row">
      <div class="stat"><div class="stat-label">High 30gg</div><div class="stat-value">$${s.high_30d.toFixed(2)}</div></div>
      <div class="stat"><div class="stat-label">Low 30gg</div><div class="stat-value">$${s.low_30d.toFixed(2)}</div></div>
      <div class="stat"><div class="stat-label">Range 30gg</div><div class="stat-value">${s.range_30d_pct.toFixed(1)}%</div></div>
      <div class="stat"><div class="stat-label">Signal</div><div class="stat-value">${s.signal || '—'}</div></div>
    </div>
    <table>
      <thead><tr><th>Time</th><th>Close</th><th>BB+</th><th>BB-</th><th>ROC%</th><th>ATR</th><th>CD</th></tr></thead>
      <tbody>${s.candles.slice(-8).map(c =>
        `<tr><td>${c.ts}</td><td>${c.close.toFixed(4)}</td><td>${c.bb_plus.toFixed(4)}</td><td>${c.bb_minus.toFixed(4)}</td>
         <td>${c.roc.toFixed(2)}</td><td>${c.atr.toFixed(4)}</td><td>${c.cd_name}</td></tr>`
      ).join('')}</tbody>
    </table>
  </div>`;
}

document.querySelectorAll('.nav-link').forEach(a => {
  if ((ACTIVE_FILTER === '' && a.classList.contains('all')) ||
      (a.getAttribute('href') === '/' + ACTIVE_FILTER)) {
    a.classList.add('active');
  }
});

refresh();
setInterval(refresh, 60000);
</script>
</body>
</html>
"""


def render(filter_id=""):
    response = make_response(render_template_string(DASHBOARD_HTML.replace("__ACTIVE_FILTER__", filter_id)))
    response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
    response.headers["Pragma"] = "no-cache"
    response.headers["Expires"] = "0"
    return response


@app.route("/")
def index():
    return render("")


@app.route("/<asset_id>")
def asset_page(asset_id):
    valid = [s["id"] for s in SETUPS]
    if asset_id not in valid:
        return render(""), 404
    return render(asset_id)


@app.route("/api/all")
def api_all():
    try:
        ex = get_client()
        return jsonify({"setups": [fetch_setup_full(ex, s) for s in SETUPS]})
    except Exception as e:
        return jsonify({"error": str(e), "setups": []}), 500


@app.route("/api/<asset_id>")
def api_one(asset_id):
    setup = next((s for s in SETUPS if s["id"] == asset_id), None)
    if not setup:
        return jsonify({"error": "not found", "setups": []}), 404
    try:
        ex = get_client()
        return jsonify({"setups": [fetch_setup_full(ex, setup)]})
    except Exception as e:
        return jsonify({"error": str(e), "setups": []}), 500


if __name__ == "__main__":
    app.run(host="127.0.0.1", port=5001, debug=False)
