"""
Metrics Tracker — computes and persists trading performance metrics from the DB.

All metrics are derived directly from the `trades` table on every cycle call.
No in-memory trade state is kept: the tracker is stateless across pod restarts.

Metric definitions
──────────────────
unrealized_pnl_est  Estimated PnL for OPEN positions: edge_net × net_cost − fee.
                    Source: open trades with edge_net. Estimated (model signal).
realized_pnl        Exact PnL for CLOSED positions: computed from resolution.
                    Source: closed trades with known resolution. Exact.
total_pnl           unrealized_pnl_est + realized_pnl.
win_rate            Fraction of resolved closed trades with close_pnl > 0.
                    NULL if fewer than 5 resolved trades.
calibration_score   1 − AVG((final_prob − resolution)²) on resolved trades.
                    Brier score (higher = better calibration). NULL if < 10 resolved.
sharpe_ratio        Annualized Sharpe of the daily total_pnl curve (see
                    bot/metrics/sharpe.py). NULL until the sample gate passes:
                    >= 30 days observed AND >= 10 resolved trades.
"""
import logging
import os
from datetime import datetime, UTC

from bot.data.db import Database
from bot.metrics.sharpe import sharpe_with_gate

log = logging.getLogger(__name__)


class MetricsTracker:
    def __init__(self, db: Database) -> None:
        self._db = db

    async def update_daily_summary(self) -> None:
        """Compute metrics from DB and write a metrics_daily snapshot.

        Called every cycle by the trading loop. Safe after pod restarts:
        reads the full trade history from DB, not from in-memory state.
        """
        raw = await self._db.compute_metrics_from_db()
        if not raw["total_trades"]:
            return

        open_count    = int(raw["open_count"] or 0)
        closed_count  = int(raw["closed_count"] or 0)
        resolved      = int(raw["resolved_count"] or 0)
        wins          = int(raw["wins_realized"] or 0)
        unrealized    = float(raw["unrealized_pnl_est"] or 0)
        realized      = float(raw["realized_pnl"] or 0)
        total_deployed = float(raw["total_deployed"] or 0)
        total_fees    = float(raw["total_fees"] or 0)
        total_pnl     = unrealized + realized

        # win_rate: only over resolved closed trades; null if sample too small
        win_rate = (wins / resolved) if resolved >= 5 else None

        # calibration: Brier score from DB; null if sample too small
        calibration = (
            float(raw["calibration_score"])
            if raw["calibration_score"] is not None and resolved >= 10
            else None
        )

        avg_edge = total_pnl / total_deployed if total_deployed > 0 else 0.0

        # Sharpe: real value from the daily PnL curve, NULL while the sample
        # gate (>=30 days observed, >=10 resolved) is not met.
        bankroll = float(os.getenv("PAPER_BANKROLL", "10000"))
        daily_closes = await self._db.get_daily_pnl_closes()
        sharpe, sharpe_status = sharpe_with_gate(daily_closes, bankroll, resolved)

        metrics = {
            "timestamp":          datetime.now(UTC),
            "total_trades":       int(raw["total_trades"]),
            "open_count":         open_count,
            "closed_count":       closed_count,
            "resolved_count":     resolved,
            "total_deployed":     total_deployed,
            "total_fees":         total_fees,
            "unrealized_pnl_est": unrealized,
            "realized_pnl":       realized,
            "total_pnl":          total_pnl,
            "win_rate":           win_rate,
            "avg_edge":           avg_edge,
            "sharpe_ratio":       sharpe,   # NULL until sample gate passes
            "calibration_score":  calibration,
            "paper_mode":         True,
        }
        await self._db.save_daily_metrics(metrics)

        log.info(
            "Daily metrics | trades=%d (open=%d closed=%d resolved=%d) | "
            "unrealized=$%.2f realized=$%.2f total=$%.2f | "
            "win_rate=%s calibration=%s sharpe=%s",
            metrics["total_trades"], open_count, closed_count, resolved,
            unrealized, realized, total_pnl,
            f"{win_rate:.1%}" if win_rate is not None else "n/a (<5)",
            f"{calibration:.3f}" if calibration is not None else "n/a (<10)",
            f"{sharpe:.2f}" if sharpe is not None else f"n/a ({sharpe_status})",
        )
