#!/usr/bin/env python3
"""
Dashboard updater — reads portfolio, fetches live prices, updates data file, generates HTML.
Run after each scan or on demand.
"""

import urllib.request
import json
import os
import re
from datetime import datetime

PORTFOLIO_PATH = "/Users/vpragent/.openclaw/workspace-joe/PORTFOLIO.md"
DATA_PATH = "/Users/vpragent/.openclaw/swing-trader/dashboard_data.json"
HTML_PATH = "/Users/vpragent/.openclaw/swing-trader/dashboard.html"
FORECASTS_PATH = "/Users/vpragent/.openclaw/workspace-karina/FORECASTS.md"

def parse_portfolio():
    """Parse PORTFOLIO.md for open positions."""
    positions = []
    try:
        with open(PORTFOLIO_PATH, "r") as f:
            content = f.read()
        # Find the table rows
        for line in content.split("\n"):
            line = line.strip()
            if line.startswith("|") and not line.startswith("| #") and not line.startswith("|--"):
                cols = [c.strip() for c in line.split("|")[1:-1]]
                if len(cols) >= 7 and cols[0].isdigit():
                    ticker = cols[1].strip()
                    direction = cols[2].strip()
                    entry = float(cols[3].replace("$", "").replace(",", ""))
                    size = float(cols[4].replace("$", "").replace(",", ""))
                    sl = float(cols[5].replace("$", "").replace(",", ""))
                    tp1 = float(cols[6].replace("$", "").replace(",", ""))
                    date_opened = cols[7].strip() if len(cols) > 7 else ""
                    positions.append({
                        "ticker": ticker,
                        "direction": direction,
                        "entry": entry,
                        "size": size,
                        "sl": sl,
                        "tp1": tp1,
                        "date_opened": date_opened
                    })
    except Exception as e:
        print(f"Error parsing portfolio: {e}")
    return positions

def fetch_price(symbol):
    """Fetch current price from Yahoo Finance."""
    try:
        url = f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}?interval=1d&range=5d"
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=10) as r:
            data = json.loads(r.read())
            meta = data["chart"]["result"][0]["meta"]
            price = meta["regularMarketPrice"]
            prev = meta.get("chartPreviousClose", meta.get("previousClose", 0))
            day_chg = ((price - prev) / prev * 100) if prev else 0
            return {"price": price, "prev_close": prev, "day_change_pct": round(day_chg, 2)}
    except Exception as e:
        return {"price": 0, "prev_close": 0, "day_change_pct": 0, "error": str(e)}

def fetch_market_data():
    """Fetch key market indices."""
    symbols = {
        "S&P 500": "^GSPC", "Nasdaq": "^IXIC", "VIX": "^VIX",
        "DXY": "DX-Y.NYB", "Oil": "CL=F", "Gold": "GC=F",
        "10Y": "^TNX", "BTC": "BTC-USD"
    }
    results = {}
    for name, sym in symbols.items():
        results[name] = fetch_price(sym)
    return results

def update_data():
    """Update dashboard data with latest snapshot."""
    # Load existing data
    try:
        with open(DATA_PATH, "r") as f:
            data = json.load(f)
    except:
        data = {"snapshots": [], "closed_trades": [], "scan_history": []}

    # Parse positions and fetch prices
    positions = parse_portfolio()
    now = datetime.now().strftime("%Y-%m-%d %H:%M")
    
    total_pnl = 0
    total_invested = 0
    pos_data = []
    
    for pos in positions:
        quote = fetch_price(pos["ticker"])
        price = quote["price"]
        entry = pos["entry"]
        size = pos["size"]
        shares = size / entry
        pnl = (price - entry) * shares if pos["direction"] == "LONG" else (entry - price) * shares
        pnl_pct = ((price - entry) / entry * 100) if pos["direction"] == "LONG" else ((entry - price) / entry * 100)
        dist_sl = abs(price - pos["sl"]) / price * 100
        dist_tp = abs(pos["tp1"] - price) / price * 100
        
        total_pnl += pnl
        total_invested += size
        
        pos_data.append({
            "ticker": pos["ticker"],
            "direction": pos["direction"],
            "entry": entry,
            "size": size,
            "sl": pos["sl"],
            "tp1": pos["tp1"],
            "date_opened": pos["date_opened"],
            "current_price": round(price, 2),
            "day_change_pct": quote["day_change_pct"],
            "pnl": round(pnl, 2),
            "pnl_pct": round(pnl_pct, 2),
            "dist_sl_pct": round(dist_sl, 2),
            "dist_tp_pct": round(dist_tp, 2)
        })
    
    # Fetch market data
    market = fetch_market_data()
    
    snapshot = {
        "timestamp": now,
        "positions": pos_data,
        "total_pnl": round(total_pnl, 2),
        "total_invested": total_invested,
        "total_pnl_pct": round(total_pnl / total_invested * 100, 2) if total_invested else 0,
        "market": {k: {"price": round(v["price"], 2), "change": v["day_change_pct"]} for k, v in market.items()}
    }
    
    data["snapshots"].append(snapshot)
    
    # Keep last 500 snapshots (about 2 months of 10x/day)
    if len(data["snapshots"]) > 500:
        data["snapshots"] = data["snapshots"][-500:]
    
    with open(DATA_PATH, "w") as f:
        json.dump(data, f, indent=2)
    
    return data

def generate_html(data):
    """Generate self-contained HTML dashboard."""
    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="refresh" content="300">
<title>🎰 Swing Trader Joe — Dashboard</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
<style>
  :root {{
    --bg: #0a0e17; --card: #131a2b; --border: #1e2a42;
    --text: #e0e6f0; --dim: #6b7b9e; --green: #00d97e; --red: #ff4d6a;
    --yellow: #ffc107; --blue: #4da6ff; --purple: #b266ff;
  }}
  * {{ margin:0; padding:0; box-sizing:border-box; }}
  body {{ font-family: 'SF Mono', 'Fira Code', monospace; background:var(--bg); color:var(--text); padding:20px; }}
  .header {{ display:flex; justify-content:space-between; align-items:center; margin-bottom:24px; border-bottom:1px solid var(--border); padding-bottom:16px; }}
  .header h1 {{ font-size:24px; }}
  .header .updated {{ color:var(--dim); font-size:12px; }}
  .grid {{ display:grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap:16px; margin-bottom:24px; }}
  .card {{ background:var(--card); border:1px solid var(--border); border-radius:12px; padding:20px; }}
  .card h2 {{ font-size:14px; color:var(--dim); text-transform:uppercase; letter-spacing:1px; margin-bottom:16px; }}
  .stat-row {{ display:flex; justify-content:space-between; align-items:center; padding:8px 0; border-bottom:1px solid var(--border); }}
  .stat-row:last-child {{ border-bottom:none; }}
  .stat-label {{ color:var(--dim); font-size:13px; }}
  .stat-value {{ font-size:15px; font-weight:600; }}
  .positive {{ color:var(--green); }}
  .negative {{ color:var(--red); }}
  .warning {{ color:var(--yellow); }}
  .neutral {{ color:var(--dim); }}
  table {{ width:100%; border-collapse:collapse; font-size:13px; }}
  th {{ text-align:left; color:var(--dim); font-size:11px; text-transform:uppercase; letter-spacing:0.5px; padding:8px 12px; border-bottom:2px solid var(--border); }}
  td {{ padding:10px 12px; border-bottom:1px solid var(--border); }}
  tr:hover {{ background: rgba(77,166,255,0.05); }}
  .ticker {{ font-weight:700; color:var(--blue); }}
  .badge {{ display:inline-block; padding:2px 8px; border-radius:4px; font-size:11px; font-weight:600; }}
  .badge-long {{ background:rgba(0,217,126,0.15); color:var(--green); }}
  .badge-short {{ background:rgba(255,77,106,0.15); color:var(--red); }}
  .badge-danger {{ background:rgba(255,77,106,0.2); color:var(--red); }}
  .badge-safe {{ background:rgba(0,217,126,0.1); color:var(--green); }}
  .badge-caution {{ background:rgba(255,193,7,0.15); color:var(--yellow); }}
  .summary-cards {{ display:grid; grid-template-columns:repeat(4,1fr); gap:12px; margin-bottom:24px; }}
  .summary-card {{ background:var(--card); border:1px solid var(--border); border-radius:10px; padding:16px; text-align:center; }}
  .summary-card .value {{ font-size:28px; font-weight:700; margin:8px 0; }}
  .summary-card .label {{ font-size:11px; color:var(--dim); text-transform:uppercase; letter-spacing:1px; }}
  .chart-container {{ position:relative; height:300px; }}
  .market-grid {{ display:grid; grid-template-columns:repeat(4,1fr); gap:8px; }}
  .market-item {{ text-align:center; padding:12px; background:rgba(255,255,255,0.02); border-radius:8px; }}
  .market-item .name {{ font-size:11px; color:var(--dim); }}
  .market-item .price {{ font-size:16px; font-weight:600; margin:4px 0; }}
  .market-item .change {{ font-size:13px; font-weight:600; }}
  .pnl-bar {{ height:6px; border-radius:3px; background:var(--border); overflow:hidden; margin-top:4px; }}
  .pnl-fill {{ height:100%; border-radius:3px; transition:width 0.5s; }}
  @media (max-width:768px) {{ .summary-cards {{ grid-template-columns:repeat(2,1fr); }} .market-grid {{ grid-template-columns:repeat(2,1fr); }} }}
</style>
</head>
<body>

<div class="header">
  <h1>🎰 Swing Trader Joe</h1>
  <div class="updated">Last update: <span id="lastUpdate"></span> · Auto-refresh: 5min</div>
</div>

<div class="summary-cards" id="summaryCards"></div>

<div class="grid">
  <div class="card" style="grid-column: span 2;">
    <h2>📊 Open Positions</h2>
    <table id="positionsTable">
      <thead>
        <tr><th>Ticker</th><th>Dir</th><th>Entry</th><th>Current</th><th>Today</th><th>P&L</th><th>P&L %</th><th>SL Dist</th><th>TP1 Dist</th><th>Status</th></tr>
      </thead>
      <tbody></tbody>
    </table>
  </div>
  
  <div class="card">
    <h2>🌍 Market Pulse</h2>
    <div class="market-grid" id="marketGrid"></div>
  </div>
</div>

<div class="grid">
  <div class="card" style="grid-column: span 2;">
    <h2>📈 Equity Curve</h2>
    <div class="chart-container">
      <canvas id="equityChart"></canvas>
    </div>
  </div>
  
  <div class="card">
    <h2>📋 Position Risk Map</h2>
    <div class="chart-container">
      <canvas id="riskChart"></canvas>
    </div>
  </div>
</div>

<div class="grid">
  <div class="card">
    <h2>🎯 Forecast Tracker</h2>
    <div id="forecastSection"></div>
  </div>
  
  <div class="card">
    <h2>📊 Stats</h2>
    <div id="statsSection"></div>
  </div>
</div>

<script>
const DATA = {json.dumps(data)};

function fmt(n, decimals=2) {{ return n.toLocaleString('en-US', {{minimumFractionDigits:decimals, maximumFractionDigits:decimals}}); }}
function fmtUSD(n) {{ return (n>=0?'+':'')+'\$'+fmt(Math.abs(n)); }}
function fmtPct(n) {{ return (n>=0?'+':'')+fmt(n)+'%'; }}
function cls(n) {{ return n >= 0 ? 'positive' : 'negative'; }}

function render() {{
  const snap = DATA.snapshots[DATA.snapshots.length - 1];
  if (!snap) return;
  
  document.getElementById('lastUpdate').textContent = snap.timestamp;
  
  // Summary cards
  const totalPnl = snap.total_pnl;
  const totalInvested = snap.total_invested;
  const numPos = snap.positions.length;
  const minSL = Math.min(...snap.positions.map(p=>p.dist_sl_pct));
  
  document.getElementById('summaryCards').innerHTML = `
    <div class="summary-card">
      <div class="label">Total P&L</div>
      <div class="value ${{cls(totalPnl)}}">${{fmtUSD(totalPnl)}}</div>
      <div class="${{cls(snap.total_pnl_pct)}}">${{fmtPct(snap.total_pnl_pct)}}</div>
    </div>
    <div class="summary-card">
      <div class="label">Invested</div>
      <div class="value">\$$${{fmt(totalInvested,0)}}</div>
      <div class="neutral">${{numPos}} positions</div>
    </div>
    <div class="summary-card">
      <div class="label">Nearest SL</div>
      <div class="value ${{minSL < 3 ? 'negative' : minSL < 5 ? 'warning' : 'positive'}}">${{fmt(minSL)}}%</div>
      <div class="neutral">${{snap.positions.find(p=>p.dist_sl_pct===minSL)?.ticker}}</div>
    </div>
    <div class="summary-card">
      <div class="label">S&P 500</div>
      <div class="value">${{fmt(snap.market['S&P 500']?.price || 0)}}</div>
      <div class="${{cls(snap.market['S&P 500']?.change || 0)}}">${{fmtPct(snap.market['S&P 500']?.change || 0)}}</div>
    </div>
  `;
  
  // Positions table
  const tbody = document.querySelector('#positionsTable tbody');
  tbody.innerHTML = snap.positions.map(p => {{
    const slStatus = p.dist_sl_pct < 3 ? 'badge-danger' : p.dist_sl_pct < 5 ? 'badge-caution' : 'badge-safe';
    const slLabel = p.dist_sl_pct < 3 ? '⚠️ DANGER' : p.dist_sl_pct < 5 ? '👀 WATCH' : '✅ SAFE';
    return `<tr>
      <td class="ticker">${{p.ticker}}</td>
      <td><span class="badge badge-${{p.direction.toLowerCase()}}">${{p.direction}}</span></td>
      <td>\$$${{fmt(p.entry)}}</td>
      <td>\$$${{fmt(p.current_price)}}</td>
      <td class="${{cls(p.day_change_pct)}}">${{fmtPct(p.day_change_pct)}}</td>
      <td class="${{cls(p.pnl)}}">${{fmtUSD(p.pnl)}}</td>
      <td class="${{cls(p.pnl_pct)}}">${{fmtPct(p.pnl_pct)}}</td>
      <td>${{fmt(p.dist_sl_pct)}}%</td>
      <td>${{fmt(p.dist_tp_pct)}}%</td>
      <td><span class="badge ${{slStatus}}">${{slLabel}}</span></td>
    </tr>`;
  }}).join('');
  
  // Market grid
  const mkt = snap.market;
  document.getElementById('marketGrid').innerHTML = Object.entries(mkt).map(([name, d]) => `
    <div class="market-item">
      <div class="name">${{name}}</div>
      <div class="price">${{name==='VIX'||name==='10Y' ? fmt(d.price) : fmt(d.price, d.price>1000?0:2)}}</div>
      <div class="change ${{cls(d.change)}}">${{fmtPct(d.change)}}</div>
    </div>
  `).join('');
  
  // Equity curve
  const snapshots = DATA.snapshots;
  if (snapshots.length > 1) {{
    const labels = snapshots.map(s => s.timestamp);
    const pnlData = snapshots.map(s => s.total_pnl);
    
    new Chart(document.getElementById('equityChart'), {{
      type: 'line',
      data: {{
        labels: labels,
        datasets: [{{
          label: 'Portfolio P&L ($)',
          data: pnlData,
          borderColor: pnlData[pnlData.length-1] >= 0 ? '#00d97e' : '#ff4d6a',
          backgroundColor: pnlData[pnlData.length-1] >= 0 ? 'rgba(0,217,126,0.1)' : 'rgba(255,77,106,0.1)',
          fill: true,
          tension: 0.3,
          pointRadius: 2,
          borderWidth: 2
        }}]
      }},
      options: {{
        responsive: true,
        maintainAspectRatio: false,
        plugins: {{ legend: {{ display: false }} }},
        scales: {{
          x: {{ display: true, ticks: {{ color: '#6b7b9e', maxTicksLimit: 8, font: {{size:10}} }}, grid: {{ color: '#1e2a42' }} }},
          y: {{ ticks: {{ color: '#6b7b9e', callback: v => '$'+v }}, grid: {{ color: '#1e2a42' }} }}
        }}
      }}
    }});
  }} else {{
    document.getElementById('equityChart').parentElement.innerHTML = '<p style="color:var(--dim);text-align:center;padding:40px;">Equity curve populates after multiple snapshots. Check back after the next scan.</p>';
  }}
  
  // Risk chart (horizontal bar — distance to SL and TP for each position)
  new Chart(document.getElementById('riskChart'), {{
    type: 'bar',
    data: {{
      labels: snap.positions.map(p => p.ticker),
      datasets: [
        {{
          label: 'Distance to SL (%)',
          data: snap.positions.map(p => -p.dist_sl_pct),
          backgroundColor: snap.positions.map(p => p.dist_sl_pct < 3 ? '#ff4d6a' : p.dist_sl_pct < 5 ? '#ffc107' : '#00d97e'),
          borderRadius: 4
        }},
        {{
          label: 'Distance to TP1 (%)',
          data: snap.positions.map(p => p.dist_tp_pct),
          backgroundColor: '#4da6ff',
          borderRadius: 4
        }}
      ]
    }},
    options: {{
      indexAxis: 'y',
      responsive: true,
      maintainAspectRatio: false,
      plugins: {{ legend: {{ labels: {{ color: '#6b7b9e' }} }} }},
      scales: {{
        x: {{ ticks: {{ color: '#6b7b9e', callback: v => Math.abs(v)+'%' }}, grid: {{ color: '#1e2a42' }} }},
        y: {{ ticks: {{ color: '#e0e6f0', font: {{ weight: 'bold' }} }}, grid: {{ display: false }} }}
      }}
    }}
  }});
  
  // Stats
  const stats = document.getElementById('statsSection');
  const wins = DATA.closed_trades.filter(t => t.pnl > 0).length;
  const losses = DATA.closed_trades.filter(t => t.pnl <= 0).length;
  const totalTrades = DATA.closed_trades.length;
  const winRate = totalTrades > 0 ? (wins/totalTrades*100).toFixed(1) : '—';
  const avgWin = wins > 0 ? (DATA.closed_trades.filter(t=>t.pnl>0).reduce((a,t)=>a+t.pnl,0)/wins).toFixed(2) : '—';
  const avgLoss = losses > 0 ? (DATA.closed_trades.filter(t=>t.pnl<=0).reduce((a,t)=>a+t.pnl,0)/losses).toFixed(2) : '—';
  
  stats.innerHTML = `
    <div class="stat-row"><span class="stat-label">Total Closed Trades</span><span class="stat-value">${{totalTrades || 'None yet'}}</span></div>
    <div class="stat-row"><span class="stat-label">Win Rate</span><span class="stat-value">${{winRate}}${{totalTrades?'%':''}}</span></div>
    <div class="stat-row"><span class="stat-label">Avg Win</span><span class="stat-value positive">${{avgWin !== '—' ? '$'+avgWin : '—'}}</span></div>
    <div class="stat-row"><span class="stat-label">Avg Loss</span><span class="stat-value negative">${{avgLoss !== '—' ? '$'+avgLoss : '—'}}</span></div>
    <div class="stat-row"><span class="stat-label">Snapshots Recorded</span><span class="stat-value">${{DATA.snapshots.length}}</span></div>
    <div class="stat-row"><span class="stat-label">Tracking Since</span><span class="stat-value">${{DATA.snapshots[0]?.timestamp || '—'}}</span></div>
  `;
  
  // Forecasts
  const fc = document.getElementById('forecastSection');
  fc.innerHTML = `
    <div class="stat-row"><span class="stat-label">Active Forecasts</span><span class="stat-value">See FORECASTS.md</span></div>
    <div class="stat-row"><span class="stat-label">Auto-scored at</span><span class="stat-value">4:05 PM ET daily</span></div>
    <p style="color:var(--dim);font-size:12px;margin-top:12px;">Forecast history will populate as scores come in.</p>
  `;
}}

render();
</script>
</body>
</html>"""
    
    with open(HTML_PATH, "w") as f:
        f.write(html)
    print(f"Dashboard generated: {HTML_PATH}")

if __name__ == "__main__":
    print("Updating dashboard data...")
    data = update_data()
    print(f"Snapshot recorded. Total snapshots: {len(data['snapshots'])}")
    generate_html(data)
    print("Done!")
