"""DASHBOARD web del portafoglio attivo (TP01 + XS01 + VRP01 + SKH01) — monitoraggio PAPER, stdlib only.

Mostra: metriche (FULL/HOLD Sharpe, DD, CAGR), per-sleeve, posizioni correnti, equity (backtest +
paper forward da scripts/live/paper_portfolio.py), ultima data dato. Nessuna auth -> solo rete
interna. Esecuzione REALE disabilitata: e' un monitor, non un trader.

  uv run python -m src.live.dashboard --port 8787
"""
from __future__ import annotations
import sys, json, time
from pathlib import Path
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd
from src.portfolio.portfolio import StrategyPortfolio, metrics, HOLDOUT
from src.portfolio.sleeves import active_sleeves, book_trade_frequency
from src.portfolio.gtaa import gtaa_weights
from src.live.shadow import shadow_report, tp01_trades
from src.version import APP_VERSION

PAPER = PROJECT_ROOT / "data" / "paper_portfolio" / "state.json"
PREVDAY = PROJECT_ROOT / "data" / "paper_prevday" / "state.json"
COMBO = PROJECT_ROOT / "data" / "paper_combo" / "state.json"
_CACHE = {"t": 0.0, "data": None}
_TTL = 120.0


def build():
    if _CACHE["data"] is not None and time.time() - _CACHE["t"] < _TTL:
        return _CACHE["data"]
    pf = StrategyPortfolio(active_sleeves(), capital=2000.0)
    bt = pf.backtest()
    eq = bt["equity"]; idx = bt["index"]
    # BOOK DERIBIT-ONLY eseguibile (TP01 75% + SKH01 25%), riusando i daily gia' calcolati
    try:
        sl_by = {s.name: s for s in pf.sleeves}
        tp_d, sk_d = sl_by["TP01_trend_1d"].daily(), sl_by["SKH01_skyhook"].daily()
        Jd = pd.concat({"t": tp_d, "s": sk_d}, axis=1, join="inner").fillna(0.0)
        der = 0.75 * Jd["t"] + 0.25 * Jd["s"]
        mm = ((1.0 + der).resample("ME").prod() - 1.0).dropna()
        d_cagr = float(np.prod(1.0 + mm.values) ** (12.0 / len(mm)) - 1.0) if len(mm) else 0.0
        cons = d_cagr * 0.5
        rd = (1.0 + cons) ** (1.0 / 365.0) - 1.0 if cons > 0 else 0.0
        deribit = dict(full=metrics(der), hold=metrics(der[der.index >= HOLDOUT]),
                       cagr=d_cagr, cons_cagr=cons, eday_5k=5000.0 * rd,
                       eq5_5y=5000.0 * (1.0 + cons) ** 5, eq5_10y=5000.0 * (1.0 + cons) ** 10)
    except Exception as e:
        deribit = {"error": f"{type(e).__name__}: {e}"}
    # sparkline: subsample ~400 punti
    step = max(1, len(eq) // 400)
    spark = [(str(idx[i].date()), float(eq[i])) for i in range(0, len(eq), step)]
    paper = json.loads(PAPER.read_text()) if PAPER.exists() else None
    prevday = json.loads(PREVDAY.read_text()) if PREVDAY.exists() else None
    combo = json.loads(COMBO.read_text()) if COMBO.exists() else None
    try:
        gtaa_w = gtaa_weights()                               # pesi ETF correnti azionabili (cache eq_*)
    except Exception:
        gtaa_w = None
    try:
        shadow = shadow_report()                              # mainnet sola lettura, best-effort
    except Exception as e:
        shadow = {"error": f"{type(e).__name__}: {e}"}
    try:
        trades = tp01_trades(limit=15)                        # entry/exit TP01 dal segnale causale
    except Exception:
        trades = []
    try:
        freq = book_trade_frequency()                         # trade/anno contati (SKH01 round-trip + TP01 turnover)
    except Exception as e:
        freq = {"error": f"{type(e).__name__}: {e}"}
    data = dict(
        version=APP_VERSION,
        last_data=str(idx[-1].date()),
        full=bt["full"], holdout=bt["holdout"], weights=bt["weights"],
        per_sleeve=bt["per_sleeve"], yearly=bt["yearly"],
        positions=pf.current_positions(), spark=spark, paper=paper, prevday=prevday,
        combo=combo, gtaa_weights=gtaa_w, deribit=deribit,
        shadow=shadow, trades=trades, freq=freq, bh=None,
    )
    _CACHE.update(t=time.time(), data=data)
    return data


def svg_spark(spark, w=900, h=220):
    ys = [v for _, v in spark]
    lo, hi = min(ys), max(ys)
    rng = hi - lo or 1
    pts = []
    for i, (_, v) in enumerate(spark):
        x = i / (len(spark) - 1) * w
        y = h - (v - lo) / rng * (h - 10) - 5
        pts.append(f"{x:.1f},{y:.1f}")
    return (f'<svg viewBox="0 0 {w} {h}" width="100%" height="{h}" preserveAspectRatio="none">'
            f'<polyline fill="none" stroke="#2ecc71" stroke-width="2" points="{" ".join(pts)}"/></svg>')


def html():
    d = build()
    f, ho = d["full"], d["holdout"]
    rows = ""
    for name, s in d["per_sleeve"].items():
        rows += (f"<tr><td>{name}</td><td>{s['weight']*100:.0f}%</td>"
                 f"<td>{s['full']['sharpe']:.2f}</td><td>{s['full']['maxdd']*100:.0f}%</td>"
                 f"<td>{s['holdout']['sharpe']:.2f}</td></tr>")
    yrs = "".join(f"<span class=y>{y}: {v['ret']*100:+.0f}%</span>" for y, v in sorted(d["yearly"].items()))
    pos = ""
    for sl, p in d["positions"].items():
        if p == {'BTC': 0.0, 'ETH': 0.0}:
            ptxt = 'flat (in cash)'
        elif p is not None:
            ptxt = str(p)
        elif 'XS01' in sl:
            ptxt = 'stat-mode (book 19 gambe)'
        elif 'SKH' in sl:
            ptxt = 'forward-monitor (segnale dual-TF, no pos-fn)'
        elif 'VRP' in sl:
            ptxt = 'stat-mode (book opzioni settimanale)'
        else:
            ptxt = 'n/d'
        pos += f"<tr><td>{sl}</td><td>{ptxt}</td></tr>"
    pp = d["paper"]
    if pp:
        days = (pd.Timestamp(pp["last"]) - pd.Timestamp(pp["start"])).days
        ret = pp["equity"] / pp["initial"] - 1
        paper_html = (f"<b>{pp['equity']:.2f}</b> (start {pp['initial']:.0f}, {pp['start'][:10]} → "
                      f"{pp['last'][:10]}, {days}g) &nbsp; ret <b>{ret*100:+.2f}%</b> &nbsp; maxDD {pp['max_dd']*100:.1f}%")
    else:
        paper_html = "non inizializzato (gira <code>paper_portfolio.py</code>)"
    pv = d.get("prevday")
    if pv:
        days = (pv["last_ts"] - pv["start_ts"]) / 86400_000
        rm = pv["cap_modeled"] / 2000.0 - 1
        rr = pv["cap_real"] / 600.0 - 1
        prevday_html = (
            f"<b>${pv['cap_modeled']:,.2f}</b> modeled (€2k nom.) &nbsp;·&nbsp; "
            f"<b>${pv['cap_real']:,.2f}</b> real-$600 (min-order $5)<br>"
            f"ret <b>{rm*100:+.2f}%</b> / <b>{rr*100:+.2f}%</b> &nbsp;·&nbsp; "
            f"maxDD {pv['dd_modeled']*100:.1f}% / {pv['dd_real']*100:.1f}% &nbsp;·&nbsp; "
            f"fill-haircut {(rm-rr)*100:+.2f}pp<br>"
            f"posizione: BTC <b>{pv['pos_real']['BTC']:+.3f}</b> · ETH <b>{pv['pos_real']['ETH']:+.3f}</b> &nbsp;·&nbsp; "
            f"{pv['n_bars']} barre 1h (~{days:.0f}g) · {pv['n_trades']} flip · "
            f"forward da {pd.Timestamp(pv['start_ts'], unit='ms').date()}")
    else:
        prevday_html = "non inizializzato (gira <code>scripts/live/paper_prevday.py</code>)"
    cb = d.get("combo")
    if cb:
        cdays = (pd.Timestamp(cb["last"]) - pd.Timestamp(cb["start"])).days
        cret = cb["equity"] / cb["initial"] - 1
        wc = cb.get("w_crypto", 0.5)
        head = (f"start {cb['initial']:.0f} · {cb['start'][:10]} → {cb['last'][:10]} ({cdays}g, "
                f"{cb['n_days']} barre) · blend {wc*100:.0f}/{(1-wc)*100:.0f} TP01/GTAA")
        combo_html = f"{head}<br><b>NUDO</b> &nbsp; eq <b>{cb['equity']:.2f}</b> &nbsp; ret <b>{cret*100:+.2f}%</b> &nbsp; maxDD {cb['max_dd']*100:.1f}%"
        if "equity_g" in cb:
            crg = cb["equity_g"] / cb["initial"] - 1
            combo_html += (f"<br><b>PROTETTO</b> (guardia-DD −{cb.get('dd_trigger',0.04)*100:.0f}%) &nbsp; "
                           f"eq <b>{cb['equity_g']:.2f}</b> &nbsp; ret <b>{crg*100:+.2f}%</b> &nbsp; maxDD {cb['max_dd_g']*100:.1f}%")
    else:
        combo_html = "non inizializzato (gira <code>scripts/live/paper_combo.py</code>)"
    gw = d.get("gtaa_weights")
    if gw:
        asof = gw.get("_asof", "?"); cash = gw.get("_cash")
        gw_html = (", ".join(f"{k} <b>{v:.0%}</b>" for k, v in gw.items() if not k.startswith("_") and v)
                   + (f" · cash <b>{cash:.0%}</b>" if cash is not None else "")
                   + f" <span style='color:#8a93a0'>(asof {asof})</span>")
    else:
        gw_html = "n/d (cache ETF assente — gira fetch_ib_equities.py)"
    db = d.get("deribit")
    if db and "error" not in db:
        f2, h2 = db["full"], db["hold"]
        deribit_html = (
            f"FULL <b>Sh {f2['sharpe']:.2f}</b> ret {f2['ret']*100:+.0f}% DD {f2['maxdd']*100:.1f}% &nbsp;·&nbsp; "
            f"HOLD-OUT <b>Sh {h2['sharpe']:.2f}</b> DD {h2['maxdd']*100:.1f}%<br>"
            f"<span style='color:#8a93a0;font-size:13px'>accumulo (reinvesti le vincite, no leva) — CAGR storico "
            f"<b>{db['cagr']*100:.0f}%</b>, conservativo <b>{db['cons_cagr']*100:.0f}%</b>: "
            f"da €5k → ~€{db['eq5_5y']:,.0f} (5a) / ~€{db['eq5_10y']:,.0f} (10a) &nbsp;·&nbsp; "
            f"run-rate oggi ~<b>€{db['eday_5k']:.2f}/g</b> @conservativo</span>")
    else:
        deribit_html = "n/d" + (f" — {db['error']}" if db and db.get("error") else "")
    sh = d.get("shadow")
    if sh and "error" not in sh:
        bits = " &nbsp;·&nbsp; ".join(
            f"{a['asset']} <b>{'FLAT' if abs(a['target'])<1e-9 else 'LONG' if a['target']>0 else 'SHORT'}</b> "
            f"{a['target']:+.2f}x" for a in sh["assets"])
        if sh.get("online"):
            eq = f"${sh['real_equity']:,.2f}" if sh.get("real_equity") else sh.get("eq_basis", "?")
            shpos = ", ".join(f"{a['asset']} ${a['position_usd']:,.0f}" for a in sh["assets"])
            ordtxt = ("; ".join(f"{o['side'].upper()} {o['amount']:.0f} {o['instrument']}" for o in sh["orders"])
                      if sh.get("orders") else "nessuno (target flat / gia' a target)")
            dsl = sh.get("disaster_sls") or []
            dsl_txt = (" · ".join(f"{x['asset']} stop <b>${x['stop']:,.0f}</b> ({x['amount']:.4f})" for x in dsl)
                       if dsl else "nessuno (flat)")
            shadow_html = (f"mainnet · sola lettura · conto reale <b>{eq}</b> · pos {shpos} · dato {sh['last_data']}<br>"
                           f"TP01 target: {bits}<br>ordini-che-invierebbe (<b>NON inviati</b>): {ordtxt}<br>"
                           f"🛡️ disaster-SL attivi (−30%): {dsl_txt}")
        else:
            shadow_html = (f"conto reale non leggibile dal container (token solo su host) · dato {sh['last_data']}<br>"
                           f"TP01 target: {bits}<br>→ per gli ordini reali: <code>uv run python scripts/live/live_trend.py</code> (host)")
    else:
        shadow_html = "non disponibile" + (f" — {sh['error']}" if sh and sh.get('error') else "")
    trows = ""
    for t in d.get("trades", []):
        cls = "g" if t["action"] == "ENTRY" else "r"
        trows += (f"<tr><td>{t['date']}</td><td>{t['asset']}</td>"
                  f"<td class={cls}>{t['action']}</td>"
                  f"<td>{t['from_pos']:+.2f} → {t['to_pos']:+.2f}x</td>"
                  f"<td>${t['price']:,.0f}</td></tr>")
    if not trows:
        trows = "<tr><td colspan=5 style='color:#8a93a0'>nessun trade ancora (TP01 flat / in cash)</td></tr>"
    live_trows = ""
    for x in ((sh.get("live_trades") if sh and "error" not in sh else None) or []):
        dcls = "g" if x["direction"] == "BUY" else "r"
        when = str(pd.Timestamp(x["ts"], unit="ms", tz="UTC"))[:16] if x["ts"] else "—"
        sym = x["instrument"].replace("_USDC-PERPETUAL", "").replace("-PERPETUAL", "")
        live_trows += (f"<tr><td>{when}</td><td>{sym}</td><td class={dcls}>{x['direction']}</td>"
                       f"<td>{x['amount']:.4f}</td><td>${x['price']:,.1f}</td><td>{x['fee']:.5f}</td></tr>")
    if not live_trows:
        live_trows = "<tr><td colspan=6 style='color:#8a93a0'>nessun trade reale eseguito (o conto non leggibile dal container)</td></tr>"
    fq = d.get("freq")
    if fq and "error" not in fq:
        skh = fq["skh"]
        by = lambda a: " ".join(f"{y}:{c}" for y, c in skh[a]["by_year"].items())
        tpy = fq["skh_combo_per_year"]
        freq_html = (
            f"<b>SKH01</b> (round-trip = apri+chiudi, no-overlap): BTC <b>~{fq['skh_per_year']['BTC']:.0f}/anno</b> · "
            f"ETH <b>~{fq['skh_per_year']['ETH']:.0f}/anno</b> → combinato <b>~{tpy:.0f}/anno</b> "
            f"(~{2*tpy:.0f} esecuzioni)<br>"
            f"<b>TP01</b> (posizione continua vol-target, NON trade discreti): turnover "
            f"<b>~{fq['tp01_turnover_per_year']:.0f}×/anno</b> per asset · zero quando flat/risk-off<br>"
            f"<span style='color:#8a93a0;font-size:13px'>→ book eseguibile ~{tpy:.0f}-{tpy+5:.0f} operazioni/anno: "
            f"poche per design (regime AND breakout). Fee ~$45-60/anno, gia' nette nei numeri storici · min-order $5 OK a $600.<br>"
            f"SKH01 round-trip/anno — BTC: {by('BTC')} · ETH: {by('ETH')}</span>")
        tpy_card = f"~{tpy:.0f}"
    else:
        freq_html = "n/d" + (f" — {fq['error']}" if fq and fq.get("error") else "")
        tpy_card = "~75"
    return f"""<!doctype html><html><head><meta charset=utf-8>
<meta http-equiv=refresh content=300><title>PythagorasGoal — Portafoglio</title>
<style>body{{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#0e1116;color:#e6e6e6;margin:0;padding:24px;max-width:980px;margin:auto}}
h1{{font-size:20px;margin:0 0 2px}}.sub{{color:#8a93a0;font-size:13px;margin-bottom:18px}}
.cards{{display:flex;gap:14px;flex-wrap:wrap;margin-bottom:18px}}
.card{{background:#161b22;border:1px solid #222b36;border-radius:10px;padding:14px 18px;min-width:150px}}
.card .k{{color:#8a93a0;font-size:12px}}.card .v{{font-size:24px;font-weight:600}}.g{{color:#2ecc71}}.r{{color:#e74c3c}}
table{{width:100%;border-collapse:collapse;margin:8px 0 20px}}td,th{{text-align:left;padding:7px 10px;border-bottom:1px solid #222b36;font-size:14px}}
th{{color:#8a93a0;font-weight:500}}.y{{display:inline-block;background:#161b22;border:1px solid #222b36;border-radius:6px;padding:3px 8px;margin:2px;font-size:12px}}
.box{{background:#161b22;border:1px solid #222b36;border-radius:10px;padding:14px 18px;margin-bottom:18px}}
.warn{{color:#f1c40f;font-size:12px}}
.section{{font-size:15px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;margin:34px 0 14px;padding:10px 14px;border-radius:9px;background:#12181f;border-left:5px solid #2ecc71;color:#d7dee6}}
.section.live{{border-left-color:#e74c3c;background:#1c1316;color:#f0c4c4}}</style></head><body>
<h1>PythagorasGoal — Portafoglio attivo (TP01 + XS01 + VRP01 + SKH01)</h1>
<div class=sub>monitor · v{d['version']} · ultimo dato {d['last_data']} · esecuzione REALE non attiva (solo micro-test) · ordine: LIVE → trades fatti → storico</div>

<div class="section live">① LIVE — Deribit mainnet (conto reale, sola lettura)</div>
<div class=box><b>Shadow TP01</b> (cosa farebbe ORA sul conto reale, nessun ordine inviato):<br>{shadow_html}</div>
<p class=warn>Solo TP01 e' armato live (cap $300/asset · disaster-SL on-book −30% · capitale reale ≈ $600). SKH01/XS01/VRP01 = paper/stat-mode. Lo "Shadow" mostra il target reale ma NON invia ordini.</p>

<div class="section">② TRADES ESEGUITI (reali) + frequenza operativa</div>
<div class=box><b>Frequenza operativa</b> (contata sui dati certificati, non stimata):<br>{freq_html}</div>
<h3 style="font-size:14px;color:#8a93a0">Trades REALI eseguiti su Deribit</h3>
<table><tr><th>data/ora UTC</th><th>strum.</th><th>dir</th><th>amount</th><th>prezzo</th><th>fee USDC</th></tr>{live_trows}</table>
<p class=warn>Oggi TP01 e' in target TSMOM risk-off → flat: nessun ordine reale. La tabella si popola quando il segnale arma una posizione e il cron giornaliero la esegue.</p>

<div class="section">③ STORICO — simulato (backtest + forward virtuale)</div>
<div class=cards>
 <div class=card><div class=k>FULL Sharpe</div><div class="v g">{f['sharpe']:.2f}</div></div>
 <div class=card><div class=k>HOLD-OUT Sharpe (2025-26)</div><div class="v g">{ho['sharpe']:.2f}</div></div>
 <div class=card><div class=k>maxDD</div><div class=v>{f['maxdd']*100:.1f}%</div></div>
 <div class=card><div class=k>CAGR</div><div class=v>{f['cagr']*100:.0f}%</div></div>
 <div class=card><div class=k>ret totale</div><div class=v>{f['ret']*100:+.0f}%</div></div>
 <div class=card><div class=k>trade/anno (book eseguibile)</div><div class=v>{tpy_card}</div></div>
</div>
<div class=box><div class=k style="color:#8a93a0;font-size:12px">EQUITY backtest (2019→oggi, €2k)</div>{svg_spark(d['spark'])}</div>
<div class=box><b>Paper forward-only:</b> {paper_html}</div>
<h3 style="font-size:14px;color:#8a93a0">Sleeve</h3>
<table><tr><th>sleeve</th><th>peso</th><th>FULL Sh</th><th>DD</th><th>HOLD Sh</th></tr>{rows}</table>
<h3 style="font-size:14px;color:#8a93a0">Posizioni correnti (ultima barra chiusa)</h3>
<table>{pos}</table>
<h3 style="font-size:14px;color:#8a93a0">Trades TP01 — entry/exit (segnale causale SIMULATO, ultimi 15)</h3>
<table><tr><th>data</th><th>asset</th><th>azione</th><th>posizione</th><th>prezzo</th></tr>{trows}</table>
<div style="margin-top:10px">{yrs}</div>
<div class="section">③·a COMBO DEPLOYABLE — cross-venue (paper, forward-only)</div>
<div class=box><b>TP01 (Deribit) + GTAA (IB)</b> — le DUE gambe ESEGUIBILI a basso capitale, scorrelate (corr ~0.21) → blend Sharpe ~1.5, drawdown dimezzato. <b>Nessuna esecuzione reale</b>:<br>{combo_html}<br>
<span style="color:#8a93a0;font-size:13px">posizioni azionabili IB (GTAA, peso ETF):</span> {gw_html}</div>
<p class=warn>⚠️ PAPER cross-venue: valida l'operativita' su due conti (Deribit + IB) a rischio zero. Lo Sharpe ~1.5 e' ottimistico (finestra crypto corta/favorevole); il dato robusto e' la diversificazione (corr 0.21, DD dimezzato), non il livello. XS01/VRP01 esclusi (STAT-MODE): qui solo TP01+GTAA.</p>
<div class="section">③·b BOOK DERIBIT-ONLY — eseguibile (TP01 + SKH01)</div>
<div class=box><b>TP01 75% + SKH01 25%</b> — le due gambe direzionali BTC/ETH sullo STESSO venue (Deribit), ribilancio mensile. Sottoinsieme realmente armabile (XS01/VRP01 esclusi):<br>{deribit_html}<br>
<span style="color:#8a93a0;font-size:13px">forecast: <code>scripts/portfolio/forecast_deribit_book.py</code> · report: <code>scripts/portfolio/run_deribit_book.py</code></span></div>
<p class=warn>⚠️ Accumulo = proiezione condizionata (storico bull crypto → pianifica sul conservativo); nessuna leva; SKH01 research/forward-monitor (solo TP01 armato live). A €50/g servono ~€177k @conservativo: la via è capitale+tempo, non leva.</p>
<div class="section">③·c FORWARD-MONITOR — lead paper (non deploy)</div>
<div class=box><b>PREVDAY range-breakout</b> — lead ORTOGONALE a TP01 (corr ~0.15 full / ~0 hold; marginal ADDS, non-hedge, robusto allo shift del confine-giorno). Forward-only, <b>nessuna esecuzione reale</b>:<br>{prevday_html}</div>
<p class=warn>⚠️ LEAD in osservazione, NON deployato. Sopravvissuto alla verifica avversariale dell'onda intraday; lo teniamo in paper per validarlo fuori-campione-vero. I due libri (modeled vs real-$600) mostrano l'haircut di fill che lo scettico aveva segnalato.</p>
<p class=warn>⚠️ Paper/monitor. XS01 e' STAT-MODE (book a 19 gambe market-neutral, non eseguibile a €2k, storia ~2.5 anni). VRP01 = lead short-vol MODELLATO (non deploy pieno). SKH01 (Skyhook dual-TF regime+breakout, BTC/ETH) = diversificatore quasi-ortogonale (corr ~0.09) aggiunto @25%: alza il FULL Sharpe del portafoglio 1.68→2.13 e dimezza il DD (14→8%) — RESEARCH/forward-monitor (book a 230m, causalita' verificata su harness ma costi reali e codice d'esecuzione da validare prima del deploy). TP01 e' l'unico deployable pieno.</p>
</body></html>"""


class H(BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass

    def do_GET(self):
        if self.path not in ("/", "/index.html"):
            self.send_response(404); self.end_headers(); return
        try:
            body = html().encode()
        except Exception as e:
            body = f"<pre>errore: {type(e).__name__}: {e}</pre>".encode()
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
        self.end_headers(); self.wfile.write(body)


def main():
    port = 8787
    if "--port" in sys.argv:
        port = int(sys.argv[sys.argv.index("--port") + 1])
    print(f"dashboard su :{port} (Ctrl-C per uscire)")
    ThreadingHTTPServer(("0.0.0.0", port), H).serve_forever()


if __name__ == "__main__":
    main()
