"""
mini_proxy.py - Reverse proxy Tailscale Funnel (porta 10000)
Mavis 2026-07-21 - v4: layout pulito + 2 PULSANTI EMERGENZA fissi in alto a destra.

Route:
- /                       → landing HTML (PC LOCALE / VPS / SERVIZI ESTERNI)
- /webhook                → http://127.0.0.1:5580 (webhook_receiver.py)
- /stats                  → http://127.0.0.1:5503 (stats_dashboard.py)
- /square                 → http://127.0.0.1:5504 (square_monitor.py)
- /tableau                → file statico G:\\AI TRADING ENGINE\\live_deploy\\tableau_de_bord.html
- /healthz                → 200 OK con info processi
- /status                 → JSON dettagliato processi
- /emergency/stop?token=X → KILL processi trading
- /emergency/restart?token=X → STOP + START processi trading

EMERGENZA (Mattia 21/07 15:36): "in live una situazione del genre mi brucio tutto il capitale".
I 2 pulsanti sono STICKY in alto a destra (sempre visibili anche scrollando).

Tailscale Funnel: https://mabest.tail2b1710.ts.net → http://127.0.0.1:10000
LINK UNICO PER MATTIA: https://mabest.tail2b1710.ts.net
"""
import os
import sys
import time
import json
import logging
import subprocess
import socket
import requests
from pathlib import Path
from flask import Flask, Response, redirect, request
from datetime import datetime

LIVE_DEPLOY = Path(r'G:\AI TRADING ENGINE\live_deploy')
LOG_DIR = LIVE_DEPLOY / 'logs'
LOG_FILE = LOG_DIR / 'mini_proxy.log'
PID_FILE = Path(r'C:\Users\Mattia\AppData\Local\Temp\mini_proxy.pid')

EMERGENCY_TOKEN = "MATTIA_STOP_2026"

TRADING_SERVICES = [
    "mavis_supervisor_3.py",
    "webhook_receiver.py",
    "rettangolo_runner.py",
    "trailing_stop_watchdog.py",
    "generate_tableau.py",
]
PYTHON_EXE = r"C:\Users\Mattia\AppData\Local\Programs\Python\Python314\python.exe"

LOG_DIR.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
)
log = logging.getLogger()

app = Flask(__name__)

WEBHOOK_PORT = 5580
STATS_PORT = 5503
NEWDASH_PORT = 5510
SQUARE_PORT = 5504

LANDING_HTML = """<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="utf-8">
<title>Mavis Trading Engine - Hub</title>
<meta http-equiv="refresh" content="60">
<style>
* {{ box-sizing: border-box; }}
body {{ font-family: 'Segoe UI', sans-serif; background: linear-gradient(135deg, #0a0e27, #1a1f3a); color: #e0e0e0; margin: 0; padding: 24px; min-height: 100vh; }}
h1 {{ color: #4fc3f7; text-align: center; font-size: 30px; margin: 0 0 6px; }}
.subtitle {{ text-align: center; color: #888; margin: 0 0 8px; font-size: 14px; }}
.main-link {{ text-align: center; margin: 0 0 32px; }}
.main-link code {{ background: #0a0e27; padding: 8px 16px; border-radius: 6px; color: #4fc3f7; font-size: 15px; border: 1px solid #2a2f4a; }}
.section-title {{ color: #4fc3f7; font-size: 16px; margin: 24px 0 12px; padding-left: 8px; border-left: 3px solid #4fc3f7; }}
.grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 14px; max-width: 1100px; margin: 0 auto; }}
.card {{ background: #1a1f3a; padding: 20px; border-radius: 10px; border: 1px solid #2a2f4a; text-decoration: none; color: inherit; transition: all 0.2s; display: block; }}
.card:hover {{ transform: translateY(-2px); border-color: #4fc3f7; box-shadow: 0 6px 20px rgba(79, 195, 247, 0.15); }}
.card h3 {{ color: #4fc3f7; margin: 0 0 6px; font-size: 17px; display: flex; align-items: center; gap: 8px; }}
.card p {{ color: #888; margin: 0; font-size: 13px; line-height: 1.4; }}
.card .url {{ color: #555; font-size: 11px; margin-top: 8px; font-family: monospace; }}
.status {{ display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 10px; font-weight: bold; margin-left: auto; }}
.status.ok {{ background: #1b5e20; color: #4caf50; }}
.status.err {{ background: #b71c1c; color: #f44336; }}
.status.unk {{ background: #555; color: #aaa; }}
.footer {{ text-align: center; color: #555; margin-top: 40px; font-size: 11px; }}
.pill {{ display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 10px; margin-right: 4px; }}
.pill.local {{ background: #1a3a5e; color: #64b5f6; }}
.pill.vps {{ background: #5e1a4a; color: #ce93d8; }}
.pill.ext {{ background: #5e4a1a; color: #ffd54f; }}

/* === PULSANTI EMERGENZA: STICKY IN ALTO A DESTRA === */
.emergency-fixed {{
    position: fixed;
    top: 16px;
    right: 16px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 8px;
    width: 200px;
}}
.emer-btn {{
    border: 2px solid;
    color: white;
    border-radius: 8px;
    padding: 12px 16px;
    font-size: 14px;
    font-weight: bold;
    cursor: pointer;
    text-align: center;
    box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}}
.emer-btn.stop {{ background: #d32f2f; border-color: #f44336; }}
.emer-btn.stop:hover {{ background: #f44336; }}
.emer-btn.restart {{ background: #2e7d32; border-color: #4caf50; }}
.emer-btn.restart:hover {{ background: #4caf50; }}
.emer-result {{
    color: #ffaaaa;
    font-size: 9px;
    text-align: center;
    max-height: 80px;
    overflow: auto;
    background: rgba(0,0,0,0.6);
    border-radius: 4px;
    padding: 4px;
}}
.emer-result.ok {{ color: #aaffaa; }}
</style>
</head>
<body>

<!-- === PULSANTI EMERGENZA STICKY IN ALTO A DESTRA === -->
<div class="emergency-fixed">
    <button onclick="emergencyAction('stop')" id="btn-stop" class="emer-btn stop">⛔ STOP TRADING</button>
    <div id="stop-result" class="emer-result"></div>
    <button onclick="emergencyAction('restart')" id="btn-restart" class="emer-btn restart">🔄 RESTART</button>
    <div id="restart-result" class="emer-result ok"></div>
</div>

<h1>🤖 Mavis Trading Engine</h1>
<p class="subtitle">Hub centrale — aggiornato {timestamp} · refresh automatico 60s</p>
<p class="main-link">Link principale: <code>https://mabest.tail2b1710.ts.net</code></p>

<div class="section-title">📍 PC LOCALE (mabest) — strategie live</div>
<div class="grid" style="grid-template-columns: repeat(3, 1fr);">
    <a class="card" href="/newdash" target="_blank">
        <h3>📈 Trading Ops Dashboard <span class="status ok">V3</span></h3>
        <p>Dashboard principale V3. Strategy Comparison e Time Analytics accessibili dal menu interno della V3.</p>
        <div class="url">/newdash · porta 5510</div>
    </a>
    <a class="card" href="/rettangolo" target="_blank">
        <h3>📐 Rettangolo Monitor <span class="status" id="s-rett">...</span></h3>
        <p>Strategia rettangolo, asset, log, posizioni aperte</p>
        <div class="url">/rettangolo · loop 60s</div>
    </a>
    <a class="card" href="/tableau" target="_blank">
        <h3>📋 Tableau de Bord <span class="status ok">OK</span></h3>
        <p>Trade journal, casistiche, statistiche per strategia/asset</p>
        <div class="url">/tableau · file statico</div>
    </a>
    <a class="card" href="/status" target="_blank">
        <h3>🔍 Status Servizi</h3>
        <p>Stato processi python + backend (UP/DOWN)</p>
        <div class="url">/status · JSON</div>
    </a>
</div>

<div class="section-title">☁️ VPS HETZNER (charter-live-vps) — 2° account, monitor mode</div>
<div class="grid">
    <a class="card" href="https://charter-live-vps.tail2b1710.ts.net/" target="_blank">
        <h3>🖥️ VPS Status Dashboard <span class="pill vps">ESTERNO</span></h3>
        <p>Stato servizi VPS, posizioni 2° account, uptime, processi</p>
        <div class="url">charter-live-vps.tail2b1710.ts.net · Tailscale Funnel</div>
    </a>
</div>

<div class="section-title">🌐 SERVIZI ESTERNI</div>
<div class="grid">
    <a class="card" href="https://uptimerobot.com/dashboard" target="_blank">
        <h3>📡 UptimeRobot <span class="pill ext">ESTERNO</span></h3>
        <p>Monitor uptime servizi · alert email se giù</p>
        <div class="url">uptimerobot.com · account bobantony58</div>
    </a>
    <a class="card" href="https://www.bybit.com/trade/spot/BTCUSDT" target="_blank">
        <h3>💱 Bybit Demo <span class="pill ext">ESTERNO</span></h3>
        <p>Exchange demo (2 account: PC locale + VPS)</p>
        <div class="url">bybit.com · Testnet Unified</div>
    </a>
    <a class="card" href="https://www.tradingview.com/" target="_blank">
        <h3>📈 TradingView <span class="pill ext">ESTERNO</span></h3>
        <p>Charting, Pine Script, alert → webhook</p>
        <div class="url">tradingview.com</div>
    </a>
    <a class="card" href="https://login.tailscale.com/admin/machines" target="_blank">
        <h3>🔐 Tailscale Admin <span class="pill ext">ESTERNO</span></h3>
        <p>Gestione macchine Tailscale + Funnel</p>
        <div class="url">login.tailscale.com</div>
    </a>
</div>

<p class="footer">
🤖 Mavis Trading Engine · 1° Agosto 2026 deadline · Bybit Demo + Tailscale Funnel + UptimeRobot<br>
Auto-refresh 60s · Per assistenza parla con Mavis (Mavis chat) · <span id="clock"></span>
</p>

<script>
const EMERGENCY_TOKEN = "MATTIA_STOP_2026";

function tick() {{
    document.getElementById('clock').textContent = 'Adesso: ' + new Date().toLocaleTimeString('it-IT');
}}
tick(); setInterval(tick, 1000);

function emergencyAction(action) {{
    if (!confirm('Sei sicuro di voler ' + action.toUpperCase() + ' tutti i processi trading?')) return;
    const btn = document.getElementById('btn-' + action);
    const result = document.getElementById(action + '-result');
    btn.disabled = true;
    const oldText = btn.innerHTML;
    btn.innerHTML = '⏳ ' + action.toUpperCase() + '...';
    result.textContent = 'Invocazione /emergency/' + action + '...';
    fetch('/emergency/' + action + '?token=' + encodeURIComponent(EMERGENCY_TOKEN), {{ method: 'POST' }})
        .then(r => r.json())
        .then(data => {{
            result.innerHTML = '<pre style="text-align:left; color:#fff; background:#000; padding:4px; border-radius:4px; overflow:auto; max-height:80px;">' + JSON.stringify(data, null, 2) + '</pre>';
            btn.disabled = false;
            btn.innerHTML = oldText;
            if (action === 'restart') setTimeout(() => location.reload(), 5000);
        }})
        .catch(e => {{
            result.textContent = 'ERRORE: ' + e;
            btn.disabled = false;
            btn.innerHTML = oldText;
        }});
}}

fetch('/healthz').then(r=>r.json()).then(d=>{{
    document.getElementById('s-stats').textContent = d.stats?'OK':'ERR';
    document.getElementById('s-stats').className = 'status ' + (d.stats?'ok':'err');
    document.getElementById('s-square').textContent = d.square?'OK':'ERR';
    document.getElementById('s-square').className = 'status ' + (d.square?'ok':'err');
    document.getElementById('s-webhook').textContent = d.webhook?'OK':'ERR';
    document.getElementById('s-webhook').className = 'status ' + (d.webhook?'ok':'err');
}}).catch(e=>{{
    document.querySelectorAll('.status').forEach(s=>{{s.textContent='?';s.className='status unk';}});
}});
</script>
</body>
</html>
"""


def _proxy(target_url: str, timeout: int = 30) -> Response:
    try:
        if request.query_string:
            sep = '&' if '?' in target_url else '?'
            target_url = f"{target_url}{sep}{request.query_string.decode('utf-8')}"
        resp = requests.request(
            method=request.method,
            url=target_url,
            headers={k: v for k, v in request.headers if k.lower() not in ('host', 'content-length')},
            data=request.get_data(),
            cookies=request.cookies,
            allow_redirects=False,
            timeout=timeout,
        )
        excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(k, v) for k, v in resp.headers.items() if k.lower() not in excluded_headers]
        body = resp.content
        # Inject MTE status banner in HTML responses
        ctype = resp.headers.get('content-type', '').lower()
        if 'text/html' in ctype and b'<body' in body.lower() and b'<head' in body.lower():
            try:
                banner = _render_mte_banner()
                body = body.replace(b'</body>', banner.encode('utf-8') + b'</body>', 1)
            except Exception as e:
                log.warning(f"banner injection failed: {e}")
        return Response(body, resp.status_code, headers)
    except requests.exceptions.RequestException as e:
        log.error(f"proxy error to {target_url}: {e}")
        return Response(f"Backend error: {e}", 502)


def _check_port(port: int, timeout: float = 1.5) -> bool:
    """True se la porta locale è in ascolto."""
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(timeout)
            s.connect(("127.0.0.1", port))
            return True
    except Exception:
        return False


def _check_service(name: str, port: int, proc_name: str = None) -> bool:
    """Verifica servizio: combinazione processo Python + porta in LISTEN.
    Se proc_name specificato, richiede ENTRAMBI (esclude falsi positivi come tailscaled sulla 10000).
    """
    port_ok = _check_port(port)
    if not proc_name:
        return port_ok
    try:
        procs = _list_python_processes()
        proc_ok = any(p["name"] == proc_name for p in procs)
    except Exception:
        proc_ok = False
    return port_ok and proc_ok


def _check_mte_services() -> dict:
    """Stato aggregato servizi Mavis Trading Engine. Ritorna dict con stato per servizio."""
    services = {
        "webhook":       {"port": WEBHOOK_PORT,    "label": "Webhook 5580",        "proc": "webhook_receiver.py"},
        "v3_dashboard":  {"port": NEWDASH_PORT,    "label": "V3 Dashboard 5510",   "proc": "stats_dashboard_live_v3.py"},
        "old_dashboard": {"port": 5503,            "label": "Old Dashboard 5503",  "proc": "stats_dashboard.py"},
        "square":        {"port": 5504,            "label": "Square Monitor 5504",  "proc": "square_monitor.py"},
        "mini_proxy":    {"port": 10000,           "label": "mini_proxy 10000",    "proc": "mini_proxy.py"},
    }
    result = {"services": {}, "ok_count": 0, "total": len(services), "status": "ok"}
    for name, info in services.items():
        up = _check_service(name, info["port"], info.get("proc"))
        info["up"] = up
        info["status"] = "UP" if up else "DOWN"
        result["services"][name] = info
        if up:
            result["ok_count"] += 1
    if result["ok_count"] == 0:
        result["status"] = "red"
    elif result["ok_count"] < result["total"]:
        result["status"] = "yellow"
    else:
        result["status"] = "green"
    return result


def _inject_banner(html: str) -> str:
    """Inietta banner semaforo MTE prima di </body>. Idempotente (no doppia injection)."""
    if 'id="mte-semaforo"' in html:
        return html
    banner = _render_mte_banner()
    lower = html.lower()
    if '</body>' in lower:
        idx = lower.rfind('</body>')
        return html[:idx] + banner + html[idx:]
    return html + banner


def _render_mte_banner() -> str:
    """Banner HTML + JS per semaforo stato MTE. Auto-refresh ogni 30s."""
    return (
        '<div id="mte-semaforo" style="position:fixed;top:0;left:0;right:0;z-index:99999;'
        'background:#1a1f3a;border-bottom:2px solid #4fc3f7;padding:8px 16px;'
        'font-family:Segoe UI,sans-serif;font-size:13px;color:#e0e0e0;'
        'display:flex;gap:16px;align-items:center;flex-wrap:wrap;box-shadow:0 2px 8px rgba(0,0,0,0.5)">'
        '<span style="font-weight:bold;color:#4fc3f7">MTE Status</span>'
        '<span id="mte-dot" style="display:inline-block;width:14px;height:14px;border-radius:50%;'
        'background:#888;box-shadow:0 0 8px currentColor"></span>'
        '<span id="mte-summary" style="font-weight:600">Caricamento...</span>'
        '<span id="mte-details" style="font-size:11px;color:#aab7c6"></span>'
        '<span style="margin-left:auto;font-size:10px;color:#666">refresh 30s</span>'
        '</div>'
        '<script>(function(){'
        'async function mteRefresh(){try{const r=await fetch("/system-status",{cache:"no-store"});'
        'const j=await r.json();const dot=document.getElementById("mte-dot");'
        'const sum=document.getElementById("mte-summary");const det=document.getElementById("mte-details");'
        'const color=j.status==="green"?"#31c48d":j.status==="yellow"?"#d29922":"#f85149";'
        'dot.style.background=color;dot.style.color=color;'
        'sum.textContent=j.ok_count+"/"+j.total+" OK";'
        'sum.style.color=color;'
        'let parts=[];for(const k in j.services){const v=j.services[k];'
        'const c=v.up?"#31c48d":"#f85149";'
        'parts.push("<span style=\"color:"+c+"\">"+v.label+":"+v.status+"</span>");}'
        'det.innerHTML=parts.join(" &middot; ");'
        '}catch(e){const sum=document.getElementById("mte-summary");'
        'if(sum)sum.textContent="errore";}}'
        'mteRefresh();setInterval(mteRefresh,30000);})();</script>'
    )


@app.route('/system-status')
def system_status():
    """Endpoint JSON con stato servizi Mavis Trading Engine. Usato dal semaforo banner."""
    state = _check_mte_services()
    return Response(json.dumps(state, ensure_ascii=False), content_type='application/json')


@app.route('/')
def index():
    html = LANDING_HTML.format(timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
    return Response(_inject_banner(html), content_type='text/html')


@app.route('/webhook', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
@app.route('/webhook/<path:subpath>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
def proxy_webhook(subpath=''):
    target = f"http://127.0.0.1:{WEBHOOK_PORT}/webhook/{subpath}" if subpath else f"http://127.0.0.1:{WEBHOOK_PORT}/webhook"
    return _proxy(target)


@app.route('/stats', methods=['GET'])
@app.route('/stats/<path:subpath>', methods=['GET'])
def proxy_stats(subpath=''):
    target = f"http://127.0.0.1:{STATS_PORT}/{subpath}" if subpath else f"http://127.0.0.1:{STATS_PORT}/"
    return _proxy(target)


@app.route('/newdash', methods=['GET'])
@app.route('/newdash/<path:subpath>', methods=['GET'])
def proxy_newdash(subpath=''):
    # /newdash → /live (main), /newdash/strategies → /strategies, ecc.
    if not subpath:
        target = f"http://127.0.0.1:{NEWDASH_PORT}/live"
    else:
        target = f"http://127.0.0.1:{NEWDASH_PORT}/{subpath}"
    return _proxy(target)


@app.route('/square', methods=['GET'])
@app.route('/square/<path:subpath>', methods=['GET'])
def proxy_square(subpath=''):
    target = f"http://127.0.0.1:{SQUARE_PORT}/{subpath}" if subpath else f"http://127.0.0.1:{SQUARE_PORT}/"
    return _proxy(target)


@app.route('/tableau')
def tableau():
    path = LIVE_DEPLOY / 'tableau_de_bord.html'
    if not path.exists():
        return Response("Tableau de bord non ancora generato (attendi 5min dall'avvio di generate_tableau.py)", 404)
    return Response(_inject_banner(path.read_text(encoding='utf-8')), content_type='text/html')


@app.route('/healthz')
def healthz():
    result = {'timestamp': datetime.now().isoformat(), 'stats': False, 'square': False, 'webhook': False, 'rettangolo': False}
    for name, port in [('stats', STATS_PORT), ('square', SQUARE_PORT), ('webhook', WEBHOOK_PORT)]:
        try:
            r = requests.get(f"http://127.0.0.1:{port}/", timeout=2)
            result[name] = r.status_code < 500
        except Exception:
            result[name] = False
    try:
        procs = _list_python_processes()
        result['rettangolo'] = any(p["name"] == "rettangolo_runner.py" for p in procs)
    except Exception:
        result['rettangolo'] = False
    return Response(json.dumps(result, indent=2), content_type='application/json')


@app.route('/rettangolo')
def rettangolo():
    """Dashboard Rettangolo: stato processo, asset, log, posizioni aperte, stats cooldown."""
    procs = _list_python_processes()
    is_up = any(p["name"] == "rettangolo_runner.py" for p in procs)
    proc = next((p for p in procs if p["name"] == "rettangolo_runner.py"), None)

    # Asset rettangolo
    rows_html = ""
    try:
        from rettangolo_config import load_assets
        for a in load_assets():
            if a.get("strategy", "rettangolo") == "rettangolo" and a.get("enabled", True):
                rows_html += f"<tr><td><b>{a.get('symbol','?')}</b></td><td>{a.get('pattern_mode','?')}</td><td>{a.get('timeframe','?')}</td><td>enabled</td></tr>"
    except Exception as e:
        rows_html = f"<tr><td colspan='4' style='color:#f44'>Errore: {e}</td></tr>"

    # Log
    log_path = LIVE_DEPLOY / "logs" / "rettangolo_stdout.log"
    log_lines = []
    if log_path.exists():
        try:
            with open(log_path, "r", encoding="utf-8", errors="replace") as f:
                log_lines = f.readlines()[-80:]
        except Exception as e:
            log_lines = [f"(errore lettura log: {e})"]
    else:
        log_lines = ["(file log non trovato)"]
    log_content = "".join(log_lines).replace("<", "&lt;").replace(">", "&gt;")

    # Stato segnali (anti-doppia cooldown)
    state_path = LIVE_DEPLOY / "logs" / "rettangolo_signal_state.json"
    state_html = "<p style='color:#888'>(nessun segnale registrato)</p>"
    if state_path.exists():
        try:
            with open(state_path, "r", encoding="utf-8") as f:
                st = json.load(f)
            if st:
                import time as _t
                now = _t.time()
                rows_st = ""
                for key, rec in st.items():
                    elapsed_h = (now - float(rec.get("ts", 0))) / 3600
                    rem_h = max(0, 4 - elapsed_h)  # cooldown 4h
                    in_cooldown = "SÌ" if rem_h > 0 else "NO"
                    cls = "#ff9800" if rem_h > 0 else "#4caf50"
                    rows_st += f"<tr><td>{key}</td><td>{rec.get('side','?')}</td><td style='color:{cls}'>{in_cooldown} ({rem_h:.1f}h rimanenti)</td><td>{rec.get('entry','?')}</td></tr>"
                state_html = f"<table style='font-size:13px'><thead><tr><th>Symbol_Side</th><th>Side</th><th>Cooldown</th><th>Entry</th></tr></thead><tbody>{rows_st}</tbody></table>"
        except Exception as e:
            state_html = f"<p style='color:#f44'>(errore: {e})</p>"

    # Posizioni aperte su simboli rettangolo
    pos_html = "<p style='color:#888'>(impossibile leggere posizioni)</p>"
    try:
        bybit = get_bybit() if 'get_bybit' in dir() else None
        if bybit is None:
            from bybit_demo_client import BybitDemoClient
            bybit = BybitDemoClient()
        positions = bybit.fetch_positions()
        rett_syms = set()
        try:
            from rettangolo_config import load_assets
            rett_syms = {a.get("symbol") for a in load_assets() if a.get("strategy","rettangolo")=="rettangolo" and a.get("enabled",True)}
        except Exception:
            pass
        rel_pos = [p for p in positions if float(p.get("size",0) or 0) > 0 and p.get("symbol") in rett_syms]
        if rel_pos:
            rows_p = ""
            for p in rel_pos:
                pnl = float(p.get("unrealisedPnl", 0) or 0)
                pcls = "#4caf50" if pnl >= 0 else "#f44336"
                rows_p += f"<tr><td>{p.get('symbol')}</td><td>{p.get('side')}</td><td>{p.get('size')}</td><td>{float(p.get('avgPrice',0) or 0):.4f}</td><td style='color:{pcls}'>{pnl:+.2f}</td></tr>"
            pos_html = f"<table style='font-size:13px'><thead><tr><th>Symbol</th><th>Side</th><th>Size</th><th>Entry</th><th>PnL</th></tr></thead><tbody>{rows_p}</tbody></table>"
        else:
            pos_html = "<p style='color:#888'>(nessuna posizione aperta su simboli rettangolo)</p>"
    except Exception as e:
        pos_html = f"<p style='color:#f44'>(errore: {e})</p>"

    proc_info = f"PID {proc['pid']}" if proc else ""

    html = f"""<!DOCTYPE html><html><head><meta charset='utf-8'><meta http-equiv='refresh' content='30'>
<title>Rettangolo Monitor</title>
<style>body{{font-family:sans-serif;background:linear-gradient(135deg,#0a0e27,#1a1f3a);color:#e0e0e0;padding:24px;margin:0;min-height:100vh}}h1{{color:#4fc3f7;margin:0 0 8px}}.s{{display:inline-block;padding:4px 10px;border-radius:4px;font-weight:bold;margin-left:8px}}.ok{{background:#1b5e20;color:#4caf50}}.er{{background:#b71c1c;color:#f44336}}.section{{background:#1a1f3a;padding:16px;border-radius:10px;margin:16px 0;max-width:1200px}}table{{width:100%;border-collapse:collapse;margin:8px 0}}th,td{{padding:8px;border-bottom:1px solid #2a2f4a;text-align:left;font-size:13px}}th{{color:#4fc3f7;font-size:11px;text-transform:uppercase}}.l{{background:#0a0e27;padding:12px;border-radius:6px;font-family:monospace;font-size:12px;max-height:500px;overflow:auto;white-space:pre-wrap;line-height:1.4}}a{{color:#4fc3f7}}</style>
</head><body>
<h1>📐 Rettangolo Monitor <span class='s {'ok' if is_up else 'er'}'>{'UP' if is_up else 'DOWN'}</span> <small style='color:#888;font-size:14px'>{proc_info}</small></h1>
<p style='color:#888;margin:0 0 16px'>refresh 30s · <a href='https://mabest.tail2b1710.ts.net'>← Mavis</a></p>

<div class='section'>
<h2 style='color:#4fc3f7;margin:0 0 8px;font-size:16px'>Asset abilitati</h2>
<table><thead><tr><th>Symbol</th><th>Pattern mode</th><th>Timeframe</th><th>Stato</th></tr></thead><tbody>{rows_html}</tbody></table>
</div>

<div class='section'>
<h2 style='color:#4fc3f7;margin:0 0 8px;font-size:16px'>Posizioni aperte (simboli rettangolo)</h2>
{pos_html}
</div>

<div class='section'>
<h2 style='color:#4fc3f7;margin:0 0 8px;font-size:16px'>Anti-doppia (segnali recenti, cooldown 4h)</h2>
{state_html}
</div>

<div class='section'>
<h2 style='color:#4fc3f7;margin:0 0 8px;font-size:16px'>Log (ultime 80 righe)</h2>
<div class='l'>{log_content}</div>
</div>

</body></html>"""
    return Response(_inject_banner(html), content_type='text/html')


@app.route('/strategy-compare')
def strategy_compare():
    """Confronto Rettangolo vs VPTR3 da last100_closed.csv."""
    import csv as _csv
    csv_path = LIVE_DEPLOY / "logs" / "last100_closed_20260720_234559.csv"
    if not csv_path.exists():
        # fallback: cerca CSV più recente
        cands = sorted((LIVE_DEPLOY / "logs").glob("last100_closed_*.csv"), key=lambda p: p.stat().st_mtime, reverse=True)
        if cands:
            csv_path = cands[0]
    stats = {}  # strategy -> dict
    last10 = []  # ultime 10 chiusure
    if csv_path.exists():
        with open(csv_path, "r", encoding="utf-8", errors="replace") as f:
            reader = _csv.DictReader(f)
            for row in reader:
                strat = (row.get("strategy") or "?").strip()
                try:
                    pnl = float(row.get("PnL USDT") or 0)
                except ValueError:
                    pnl = 0
                s = stats.setdefault(strat, {"n": 0, "wins": 0, "pnl": 0.0, "symbols": {}})
                s["n"] += 1
                s["pnl"] += pnl
                if pnl > 0:
                    s["wins"] += 1
                sym = row.get("symbol") or "?"
                s["symbols"][sym] = s["symbols"].get(sym, 0) + 1
                last10.append({
                    "ts": row.get("close_time", ""),
                    "strat": strat,
                    "sym": sym,
                    "side": row.get("side", ""),
                    "qty": row.get("qty", ""),
                    "pnl": pnl,
                    "pnl_pct": row.get("PnL% MARGINE", ""),
                })
    # Calcola WR
    for s in stats.values():
        s["wr"] = (s["wins"] / s["n"] * 100) if s["n"] else 0
    # Best/worst per strategia
    last10.sort(key=lambda r: r["ts"], reverse=True)
    last10 = last10[:10]

    def card_html(name, key, color):
        s = stats.get(key, {"n": 0, "wins": 0, "pnl": 0.0, "wr": 0, "symbols": {}})
        pnl_class = "pnl-pos" if s["pnl"] >= 0 else "pnl-neg"
        syms = ", ".join(f"{k}({v})" for k, v in sorted(s["symbols"].items(), key=lambda x: -x[1])[:5]) or "-"
        return f"""
<div class="scard" style="border: 2px solid {color};">
  <h2 style="color: {color}; margin: 0 0 12px;">{name}</h2>
  <div class="metric"><span class="label">Trade chiusi</span><span class="val">{s['n']}</span></div>
  <div class="metric"><span class="label">Win Rate</span><span class="val">{s['wr']:.1f}%</span></div>
  <div class="metric"><span class="label">Wins / Losses</span><span class="val">{s['wins']} / {s['n']-s['wins']}</span></div>
  <div class="metric"><span class="label">PnL totale</span><span class="val {pnl_class}">{s['pnl']:+.2f} USDT</span></div>
  <div class="metric"><span class="label">PnL medio</span><span class="val {pnl_class}">{(s['pnl']/s['n'] if s['n'] else 0):+.2f} USDT</span></div>
  <div class="metric"><span class="label">Simboli top</span><span class="val" style="font-size:11px;">{syms}</span></div>
</div>"""

    rett_card = card_html("📐 RETTANGOLO", "rettangolo", "#4fc3f7")
    vptr_card = card_html("📊 VPTR3", "vptr3", "#ffd54f")
    non_auth = stats.get("NON AUTORIZZATO", {"n": 0})

    rows_html = ""
    for r in last10:
        pcls = "pnl-pos" if r["pnl"] >= 0 else "pnl-neg"
        rows_html += f"<tr><td>{r['ts'][:19]}</td><td>{r['strat']}</td><td>{r['sym']}</td><td>{r['side']}</td><td>{r['qty']}</td><td class='{pcls}'>{r['pnl']:+.2f}</td><td>{r['pnl_pct']}</td></tr>"

    html = f"""<!DOCTYPE html><html><head><meta charset='utf-8'><meta http-equiv='refresh' content='60'>
<title>Strategy Comparison</title>
<style>body{{font-family:sans-serif;background:linear-gradient(135deg,#0a0e27,#1a1f3a);color:#e0e0e0;padding:24px;margin:0}}h1{{color:#4fc3f7;margin:0 0 16px}}.grid{{display:grid;grid-template-columns:1fr 1fr;gap:20px;max-width:1200px;margin:0 auto 20px}}.scard{{background:#1a1f3a;padding:20px;border-radius:10px}}.metric{{display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid #2a2f4a}}.label{{color:#888;font-size:12px;text-transform:uppercase}}.val{{font-weight:bold;font-size:16px}}.pnl-pos{{color:#4caf50}}.pnl-neg{{color:#f44336}}table{{width:100%;border-collapse:collapse;margin-top:24px}}th,td{{padding:8px;text-align:left;border-bottom:1px solid #2a2f4a;font-size:13px}}th{{color:#4fc3f7;text-transform:uppercase;font-size:11px}}.footer{{color:#555;margin-top:24px;text-align:center;font-size:11px}}</style>
</head><body>
<h1>📊 Strategy Comparison · Rettangolo vs VPTR3</h1>
<p style="color:#888;margin:0 0 16px;">Fonte: <code>{csv_path.name}</code> · refresh 60s</p>
<div class='grid'>{rett_card}{vptr_card}</div>
<h2 style='color:#4fc3f7;max-width:1200px;margin:0 auto;'>Ultime 10 chiusure</h2>
<table style='max-width:1200px;margin:0 auto;'>
<thead><tr><th>Data</th><th>Strategia</th><th>Symbol</th><th>Side</th><th>Qty</th><th>PnL USDT</th><th>PnL% Margine</th></tr></thead>
<tbody>{rows_html}</tbody>
</table>
<p class="footer">⚠ {non_auth.get('n',0)} trade NON AUTORIZZATO rilevati · Trading Engine · Mavis · 1° Agosto 2026 deadline</p>
</body></html>"""
    return Response(_inject_banner(html), content_type='text/html')


def _list_python_processes():
    out = []
    try:
        result = subprocess.run(
            ["powershell", "-NoProfile", "-Command",
             "Get-CimInstance Win32_Process -Filter \"Name='python.exe'\" | Select-Object ProcessId, CommandLine | ConvertTo-Json -Compress"],
            capture_output=True, text=True, timeout=10
        )
        procs = json.loads(result.stdout) if result.stdout.strip() else []
        if isinstance(procs, dict):
            procs = [procs]
        for p in procs:
            pid = p.get("ProcessId")
            cmd = p.get("CommandLine", "")
            name = cmd.split()[-1] if cmd.split() else ""
            out.append({"pid": pid, "name": name, "cmd": cmd})
    except Exception as e:
        log.error(f"_list_python_processes error: {e}")
    return out


def _kill_trading_services():
    killed = []
    for proc in _list_python_processes():
        for service in TRADING_SERVICES:
            if proc["name"] == service:
                try:
                    subprocess.run(["powershell", "-NoProfile", "-Command", f"Stop-Process -Id {proc['pid']} -Force"],
                                   capture_output=True, timeout=5)
                    killed.append({"service": service, "pid": proc["pid"]})
                    log.warning(f"EMERGENCY STOP: killed {service} (PID {proc['pid']})")
                except Exception as e:
                    log.error(f"kill {service} (PID {proc['pid']}) error: {e}")
    return killed


def _start_trading_services():
    started = []
    for service in TRADING_SERVICES:
        try:
            subprocess.Popen(
                [PYTHON_EXE, "-u", service],
                cwd=str(LIVE_DEPLOY),
                stdout=open(LOG_DIR / f"{service.replace('.py', '')}_stdout.log", "a"),
                stderr=open(LOG_DIR / f"{service.replace('.py', '')}_stderr.log", "a"),
                creationflags=0x00000008,
            )
            started.append({"service": service})
            log.warning(f"EMERGENCY RESTART: started {service}")
        except Exception as e:
            log.error(f"start {service} error: {e}")
    return started


@app.route('/status')
def status():
    procs = _list_python_processes()
    backends = {}
    for name, port in [('webhook', WEBHOOK_PORT), ('stats', STATS_PORT), ('square', SQUARE_PORT)]:
        try:
            r = requests.get(f"http://127.0.0.1:{port}/", timeout=2)
            backends[name] = {"port": port, "up": r.status_code < 500, "status_code": r.status_code}
        except Exception as e:
            backends[name] = {"port": port, "up": False, "error": str(e)[:80]}
    return Response(json.dumps({
        "timestamp": datetime.now().isoformat(),
        "python_processes": procs,
        "backends": backends,
        "trading_services": TRADING_SERVICES,
    }, indent=2), content_type='application/json')


def _check_emergency_token():
    token = request.args.get("token", "")
    if token != EMERGENCY_TOKEN:
        log.warning(f"EMERGENCY AUTH FAILED: token='{token[:8]}...' from {request.remote_addr}")
        return Response(json.dumps({"error": "invalid token"}), status=401, content_type='application/json')
    return None


@app.route('/emergency/stop', methods=['GET', 'POST'])
def emergency_stop():
    auth = _check_emergency_token()
    if auth:
        return auth
    log.critical(f"EMERGENCY STOP da {request.remote_addr}")
    killed = _kill_trading_services()
    time.sleep(1)
    return Response(json.dumps({
        "status": "STOPPED",
        "timestamp": datetime.now().isoformat(),
        "killed": killed,
        "survived": ["integrity_watchdog.py", "mini_proxy.py", "stats_dashboard", "square_monitor", "trade_monitor"],
    }, indent=2), content_type='application/json')


@app.route('/emergency/restart', methods=['GET', 'POST'])
def emergency_restart():
    auth = _check_emergency_token()
    if auth:
        return auth
    log.critical(f"EMERGENCY RESTART da {request.remote_addr}")
    killed = _kill_trading_services()
    time.sleep(2)
    started = _start_trading_services()
    time.sleep(3)
    return Response(json.dumps({
        "status": "RESTARTED",
        "timestamp": datetime.now().isoformat(),
        "killed": killed,
        "started": started,
    }, indent=2), content_type='application/json')


def main():
    log.info("=" * 70)
    log.info("=== MINI PROXY v4 STARTED — layout pulito + pulsanti sticky top-right ===")
    log.info("=" * 70)
    with open(PID_FILE, "w") as f:
        f.write(str(os.getpid()))
    import logging as _l
    _l.getLogger('werkzeug').setLevel(_l.WARNING)
    app.run(host='0.0.0.0', port=10000, debug=False, threaded=True)


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        log.info("STOPPED (KeyboardInterrupt)")
    except Exception as e:
        log.critical(f"FATAL: {e}")
        import traceback
        log.critical(traceback.format_exc())
        sys.exit(1)
