"""
compute_perf.py — compute daily P&L summary, send to Telegram, and generate HTML dashboard.
Run after market close (e.g., 4:00 PM ET).
"""
import csv
import json
import sys
from datetime import datetime
from pathlib import Path
from zoneinfo import ZoneInfo

import pandas as pd

from src.notify import notify

ET = ZoneInfo("America/New_York")
TRADES_PATH = Path("trades.csv")
POSITIONS_PATH = Path("open_positions.json")
SAFETY_LOG_PATH = Path("logs/safety-check-log.json")
DASHBOARD_DIR = Path("dashboard")


def cast_num(v):
    """Cast numpy/pandas types to Python native types."""
    if hasattr(v, "item"):
        return v.item()
    return v


def compute_daily_perf():
    """Compute P&L for closed trades today."""
    today_et = datetime.now(ET).date().isoformat()

    # Load trades
    if not TRADES_PATH.exists():
        trades = []
    else:
        try:
            trades = list(csv.DictReader(open(TRADES_PATH)))
        except:
            trades = []

    # Filter to today
    today_trades = [t for t in trades if t.get("timestamp_iso", "").startswith(today_et)]

    if not today_trades:
        return {
            "success": True,
            "date": today_et,
            "total_trades": 0,
            "wins": 0,
            "losses": 0,
            "win_rate_pct": 0.0,
            "gross_pnl_usd": 0.0,
            "largest_winner": None,
            "largest_loser": None,
            "avg_winner": 0.0,
            "avg_loser": 0.0,
            "profit_factor": "n/a",
        }

    # Load stop prices from open_positions.json if available
    stop_prices = {}
    if POSITIONS_PATH.exists():
        try:
            positions = json.loads(POSITIONS_PATH.read_text())
            for pos in positions:
                stop_prices[pos.get("symbol", "")] = pos.get("initial_stop", 0)
        except:
            pass

    # Pair BUY with SELL by symbol (FIFO)
    buys = {}
    closed_pairs = []

    for trade in today_trades:
        symbol = trade.get("symbol", "")
        side = trade.get("side", "")
        price = float(trade.get("fill_price", 0)) if trade.get("fill_price") else 0
        size = int(trade.get("size", 0)) if trade.get("size") else 0
        ts = trade.get("timestamp_iso", "")

        if side == "BUY":
            if symbol not in buys:
                buys[symbol] = []
            buys[symbol].append({"price": price, "size": size, "ts": ts})

        elif side == "SELL" and symbol in buys and buys[symbol]:
            buy = buys[symbol].pop(0)
            if price > 0 and buy["price"] > 0:
                pnl = (price - buy["price"]) * buy["size"]
                pnl_pct = ((price - buy["price"]) / buy["price"]) * 100
                try:
                    buy_dt = datetime.fromisoformat(buy["ts"])
                    sell_dt = datetime.fromisoformat(ts)
                    hold_minutes = (sell_dt - buy_dt).total_seconds() / 60
                except:
                    hold_minutes = 0

                # Calculate per-trade R
                initial_stop = stop_prices.get(symbol, buy["price"] * 0.99)
                risk_per_share = buy["price"] - initial_stop
                R = (price - buy["price"]) / risk_per_share if risk_per_share > 0 else 0

                closed_pairs.append({
                    "symbol": symbol,
                    "buy_price": buy["price"],
                    "sell_price": price,
                    "initial_stop": initial_stop,
                    "size": buy["size"],
                    "pnl": pnl,
                    "pnl_pct": pnl_pct,
                    "hold_minutes": hold_minutes,
                    "R": R,
                    "ts": ts,
                })

    if not closed_pairs:
        return {
            "success": True,
            "date": today_et,
            "total_trades": 0,
            "wins": 0,
            "losses": 0,
            "win_rate_pct": 0.0,
            "gross_pnl_usd": 0.0,
            "largest_winner": None,
            "largest_loser": None,
            "avg_winner": 0.0,
            "avg_loser": 0.0,
            "profit_factor": "n/a",
            "closed_pairs": [],
        }

    # Aggregate
    total = len(closed_pairs)
    pnls = [p["pnl"] for p in closed_pairs]
    winners = [p for p in closed_pairs if p["pnl"] > 0]
    losers = [p for p in closed_pairs if p["pnl"] < 0]

    wins = len(winners)
    losses = len(losers)
    win_rate = (wins / total * 100) if total > 0 else 0
    gross_pnl = sum(pnls)

    largest_winner = max(winners, key=lambda x: x["pnl"]) if winners else None
    largest_loser = min(losers, key=lambda x: x["pnl"]) if losers else None

    avg_winner = sum(p["pnl"] for p in winners) / len(winners) if winners else 0
    avg_loser = sum(p["pnl"] for p in losers) / len(losers) if losers else 0

    sum_wins = sum(p["pnl"] for p in winners) if winners else 0
    sum_losses = abs(sum(p["pnl"] for p in losers)) if losers else 0
    pf = (sum_wins / sum_losses) if sum_losses > 0 else ("inf" if sum_wins > 0 else "n/a")

    return {
        "success": True,
        "date": today_et,
        "total_trades": total,
        "wins": wins,
        "losses": losses,
        "win_rate_pct": round(win_rate, 2),
        "gross_pnl_usd": round(gross_pnl, 2),
        "largest_winner": f"{largest_winner['symbol']} ${largest_winner['pnl']:.2f}" if largest_winner else None,
        "largest_loser": f"{largest_loser['symbol']} ${largest_loser['pnl']:.2f}" if largest_loser else None,
        "avg_winner": round(avg_winner, 2),
        "avg_loser": round(avg_loser, 2),
        "profit_factor": round(pf, 2) if isinstance(pf, float) else pf,
        "closed_pairs": closed_pairs,
    }


def get_open_positions():
    """Load current open positions."""
    if not POSITIONS_PATH.exists():
        return []
    try:
        return json.loads(POSITIONS_PATH.read_text())
    except:
        return []


def get_last_cycle_info():
    """Get last cycle timestamp from safety-check-log.json."""
    if not SAFETY_LOG_PATH.exists():
        return None, "No cycle data yet"
    try:
        with open(SAFETY_LOG_PATH) as f:
            lines = f.readlines()
            if lines:
                last = json.loads(lines[-1])
                ts = last.get("timestamp", "")
                decision = last.get("decision", "")
                return ts, decision
    except:
        pass
    return None, "No cycle data yet"


def build_r_histogram(closed_pairs):
    """Build R-multiple histogram buckets."""
    buckets = {
        "(-∞, -2R]": 0,
        "(-2R, -1R]": 0,
        "(-1R, 0R]": 0,
        "(0R, +1R]": 0,
        "(+1R, +2R]": 0,
        "(+2R, +3R]": 0,
        "(+3R, +∞)": 0,
    }
    for pair in closed_pairs:
        R = pair["R"]
        if R <= -2:
            buckets["(-∞, -2R]"] += 1
        elif R <= -1:
            buckets["(-2R, -1R]"] += 1
        elif R <= 0:
            buckets["(-1R, 0R]"] += 1
        elif R <= 1:
            buckets["(0R, +1R]"] += 1
        elif R <= 2:
            buckets["(+1R, +2R]"] += 1
        elif R <= 3:
            buckets["(+2R, +3R]"] += 1
        else:
            buckets["(+3R, +∞)"] += 1
    return buckets


def generate_html_dashboard(perf, open_positions, cycle_ts, cycle_status):
    """Generate HTML dashboard."""
    DASHBOARD_DIR.mkdir(exist_ok=True)

    closed_pairs = perf.get("closed_pairs", [])
    r_histogram = build_r_histogram(closed_pairs)
    max_bucket = max(r_histogram.values()) if r_histogram.values() else 1

    # Recent trades (last 20)
    recent_trades_html = ""
    if closed_pairs:
        for pair in closed_pairs[-20:]:
            r_color = "success" if pair["R"] > 0 else "danger"
            recent_trades_html += f"""
            <tr>
              <td>{pair["symbol"]}</td>
              <td>{pair["size"]}</td>
              <td>${pair["buy_price"]:.2f}</td>
              <td>${pair["sell_price"]:.2f}</td>
              <td>${pair["pnl"]:+.2f}</td>
              <td><span class="badge bg-{r_color}">{pair["R"]:.2f}R</span></td>
            </tr>
            """

    # Open positions table
    open_pos_html = ""
    if open_positions:
        for pos in open_positions:
            open_pos_html += f"""
            <tr>
              <td>{pos.get("symbol", "")}</td>
              <td>{pos.get("qty", "")}</td>
              <td>${pos.get("entry_price", 0):.2f}</td>
              <td>${pos.get("initial_stop", 0):.2f}</td>
              <td>{pos.get("state", "")}</td>
            </tr>
            """

    # R histogram bars
    histogram_html = ""
    for bucket, count in r_histogram.items():
        width_pct = (count / max_bucket * 100) if max_bucket > 0 else 0
        histogram_html += f"""
        <div class="mb-3">
          <div class="d-flex justify-content-between mb-1">
            <small><strong>{bucket}</strong></small>
            <small>{count} trades</small>
          </div>
          <div class="progress" style="height: 24px;">
            <div class="progress-bar bg-info" style="width: {width_pct}%"></div>
          </div>
        </div>
        """

    cycle_time_str = cycle_ts if cycle_ts else "—"
    bot_status = "ACTIVE" if perf["total_trades"] > 0 else "IDLE"

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta http-equiv="refresh" content="300">
  <title>IBKR Bot Dashboard</title>
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
  <style>
    body {{ background-color: #f8f9fa; }}
    .card {{ margin-bottom: 1.5rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }}
    .header-strip {{ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 8px; margin-bottom: 2rem; }}
    .metric {{ text-align: center; padding: 15px; }}
    .metric-value {{ font-size: 2rem; font-weight: bold; color: #667eea; }}
    .metric-label {{ color: #666; font-size: 0.9rem; }}
  </style>
</head>
<body>
  <div class="container-fluid py-4">
    <!-- Header -->
    <div class="header-strip">
      <div class="row align-items-center">
        <div class="col-md-6">
          <h1 class="mb-0">🤖 IBKR Paper-Trading Bot</h1>
          <p class="mb-0">Status: <strong>{bot_status}</strong></p>
        </div>
        <div class="col-md-6 text-end">
          <small>Last Cycle: {cycle_time_str}</small><br>
          <small>{perf["date"]}</small>
        </div>
      </div>
    </div>

    <!-- P&L Summary -->
    <div class="card">
      <div class="card-header bg-dark text-white">
        <h5 class="mb-0">Today's P&L Summary</h5>
      </div>
      <div class="card-body">
        <div class="row">
          <div class="col-md-3 metric">
            <div class="metric-value">{perf["gross_pnl_usd"]:+.2f}</div>
            <div class="metric-label">Total P&L</div>
          </div>
          <div class="col-md-3 metric">
            <div class="metric-value">{perf["total_trades"]}</div>
            <div class="metric-label">Closed Trades</div>
          </div>
          <div class="col-md-3 metric">
            <div class="metric-value">{perf["wins"]}W / {perf["losses"]}L</div>
            <div class="metric-label">Wins / Losses</div>
          </div>
          <div class="col-md-3 metric">
            <div class="metric-value">{perf["win_rate_pct"]}%</div>
            <div class="metric-label">Win Rate</div>
          </div>
        </div>
      </div>
    </div>

    <!-- R-Multiple Histogram -->
    <div class="card">
      <div class="card-header bg-dark text-white">
        <h5 class="mb-0">R-Multiple Distribution</h5>
      </div>
      <div class="card-body">
        {histogram_html if closed_pairs else "<p class='text-muted'>No closed trades yet.</p>"}
      </div>
    </div>

    <!-- Open Positions -->
    <div class="card">
      <div class="card-header bg-dark text-white">
        <h5 class="mb-0">Open Positions ({len(open_positions)})</h5>
      </div>
      <div class="card-body">
        {f'''<table class="table table-sm">
          <thead>
            <tr>
              <th>Symbol</th>
              <th>Qty</th>
              <th>Entry</th>
              <th>Stop</th>
              <th>State</th>
            </tr>
          </thead>
          <tbody>
            {open_pos_html if open_positions else "<tr><td colspan='5' class='text-muted'>No open positions.</td></tr>"}
          </tbody>
        </table>''' if open_positions else "<p class='text-muted'>No open positions.</p>"}
      </div>
    </div>

    <!-- Recent Closed Trades -->
    <div class="card">
      <div class="card-header bg-dark text-white">
        <h5 class="mb-0">Recent Closed Trades (Last 20)</h5>
      </div>
      <div class="card-body">
        {f'''<table class="table table-sm">
          <thead>
            <tr>
              <th>Symbol</th>
              <th>Qty</th>
              <th>Buy Price</th>
              <th>Sell Price</th>
              <th>P&L</th>
              <th>R Multiple</th>
            </tr>
          </thead>
          <tbody>
            {recent_trades_html if closed_pairs else "<tr><td colspan='6' class='text-muted'>No closed trades yet.</td></tr>"}
          </tbody>
        </table>''' if closed_pairs else "<p class='text-muted'>No closed trades yet.</p>"}
      </div>
    </div>

    <footer class="text-center py-4 text-muted">
      <small>Auto-generated by compute_perf.py • Updated: {datetime.now(ET).strftime('%Y-%m-%d %H:%M:%S %Z')}</small>
    </footer>
  </div>
</body>
</html>
"""

    dashboard_file = DASHBOARD_DIR / "index.html"
    with open(dashboard_file, "w") as f:
        f.write(html)

    return dashboard_file


def main():
    perf = compute_daily_perf()

    # Preserve existing behavior: JSON stdout
    perf_out = {k: v for k, v in perf.items() if k != "closed_pairs"}
    print(json.dumps(perf_out, indent=2))

    # Preserve existing behavior: Telegram notification
    try:
        if perf["total_trades"] == 0:
            body = "No closed trades today."
        else:
            body = (
                f"Trades: {perf['total_trades']} "
                f"({perf['wins']}W / {perf['losses']}L, {perf['win_rate_pct']}%)\n"
                f"P&L: ${perf['gross_pnl_usd']:+.2f}\n"
                f"Best: {perf['largest_winner'] or 'N/A'}\n"
                f"Worst: {perf['largest_loser'] or 'N/A'}\n"
                f"PF: {perf['profit_factor']}"
            )
        notify(f"Daily Summary {perf['date']}", body, "default")
    except:
        pass

    # NEW: Generate HTML dashboard
    try:
        open_positions = get_open_positions()
        cycle_ts, cycle_status = get_last_cycle_info()
        dashboard_file = generate_html_dashboard(perf, open_positions, cycle_ts, cycle_status)
        print(f"Dashboard saved to {dashboard_file}")
    except Exception as e:
        print(f"[WARN] Dashboard generation failed: {e}")


if __name__ == "__main__":
    main()
