"""BacktestResult container for backtest output."""

from __future__ import annotations

import csv
from dataclasses import dataclass, field

from horizon.analytics import Metrics, compute_metrics


@dataclass
class BacktestResult:
    """Container for backtest results with lazy metric computation."""

    equity_curve: list[tuple[float, float]] = field(default_factory=list)
    trades: list = field(default_factory=list)
    positions: list = field(default_factory=list)
    initial_capital: float = 1000.0
    outcomes: dict[str, float] | None = None

    _metrics: Metrics | None = field(default=None, repr=False)

    @property
    def metrics(self) -> Metrics:
        """Lazy-compute metrics on first access."""
        if self._metrics is None:
            self._metrics = compute_metrics(
                self.equity_curve, self.trades, self.initial_capital, self.outcomes,
            )
        return self._metrics

    def pnl_by_market(self) -> dict[str, float]:
        """Compute realized PnL per market from trades (FIFO matching with fees)."""
        from collections import defaultdict, deque

        # market -> deque of (price, size, fee)
        buys: dict[str, deque[tuple[float, float, float]]] = defaultdict(deque)
        pnl: dict[str, float] = defaultdict(float)

        for fill in self.trades:
            market_id = getattr(fill, "market_id", "")
            price = getattr(fill, "price", 0.0)
            size = getattr(fill, "size", 0.0)
            fee = getattr(fill, "fee", 0.0) or 0.0
            side_str = str(getattr(fill, "order_side", "")).lower()
            is_buy = "buy" in side_str

            if is_buy:
                buys[market_id].append((price, size, fee))
            else:
                remaining = size
                trade_pnl = 0.0
                buy_fee_total = 0.0
                while remaining > 0 and buys[market_id]:
                    buy_price, buy_size, buy_fee = buys[market_id][0]
                    matched = min(remaining, buy_size)
                    trade_pnl += matched * (price - buy_price)
                    buy_fee_total += buy_fee * (matched / buy_size) if buy_size > 0 else 0.0
                    remaining -= matched
                    if matched >= buy_size:
                        buys[market_id].popleft()
                    else:
                        remaining_buy_fee = buy_fee * ((buy_size - matched) / buy_size) if buy_size > 0 else 0.0
                        buys[market_id][0] = (buy_price, buy_size - matched, remaining_buy_fee)
                pnl[market_id] += trade_pnl - fee - buy_fee_total

        return dict(pnl)

    def summary(self) -> str:
        """Format a human-readable summary of backtest results."""
        m = self.metrics
        lines = [
            "=" * 50,
            "  BACKTEST RESULTS",
            "=" * 50,
            "",
            "--- Returns ---",
            f"  Initial Capital:   ${self.initial_capital:,.2f}",
            f"  Final Equity:      ${self.equity_curve[-1][1]:,.2f}" if self.equity_curve else "  Final Equity:      N/A",
            f"  Total Return:      ${m.total_return:,.2f} ({m.total_return_pct:+.2f}%)",
            f"  CAGR:              {m.cagr * 100:.2f}%",
            "",
            "--- Risk ---",
            f"  Sharpe Ratio:      {m.sharpe_ratio:.3f}",
            f"  Sortino Ratio:     {m.sortino_ratio:.3f}",
            f"  Calmar Ratio:      {m.calmar_ratio:.3f}",
            f"  Max Drawdown:      ${m.max_drawdown:,.2f} ({m.max_drawdown_pct:.2f}%)",
            f"  Max DD Duration:   {m.max_drawdown_duration_secs:.0f}s",
            "",
            "--- Trades ---",
            f"  Total Trades:      {m.total_trades}",
            f"  Win Rate:          {m.win_rate:.1f}%",
            f"  Profit Factor:     {m.profit_factor:.2f}",
            f"  Expectancy:        ${m.expectancy:,.4f}",
            f"  Avg Win:           ${m.avg_win:,.4f}",
            f"  Avg Loss:          ${m.avg_loss:,.4f}",
            f"  Largest Win:       ${m.largest_win:,.4f}",
            f"  Largest Loss:      ${m.largest_loss:,.4f}",
            f"  Total Fees:        ${m.total_fees:,.4f}",
        ]

        if m.brier_score is not None:
            lines += [
                "",
                "--- Prediction Market ---",
                f"  Brier Score:       {m.brier_score:.4f}",
                f"  Avg Edge:          {m.avg_edge:.4f}" if m.avg_edge is not None else "",
            ]

        lines += [
            "",
            "=" * 50,
        ]
        return "\n".join(lines)

    def calibration(self, n_bins: int = 10):
        """Compute calibration curve from buy trades + self.outcomes.

        Returns a CalibrationResult with bins, brier_score, log_loss, ece.
        Requires outcomes to be set.
        """
        if not self.outcomes:
            raise ValueError("outcomes must be set to compute calibration")

        from horizon._horizon import calibration_curve
        from horizon.analytics import _extract_predictions_outcomes

        predictions, outcome_vals = _extract_predictions_outcomes(
            self.trades, self.outcomes
        )
        if not predictions:
            raise ValueError("no buy trades with matching outcomes found")

        return calibration_curve(predictions, outcome_vals, n_bins)

    def log_loss_score(self) -> float:
        """Compute log-loss from buy trades + self.outcomes.

        Returns the log-loss value. Requires outcomes to be set.
        """
        if not self.outcomes:
            raise ValueError("outcomes must be set to compute log-loss")

        from horizon._horizon import log_loss
        from horizon.analytics import _extract_predictions_outcomes

        predictions, outcome_vals = _extract_predictions_outcomes(
            self.trades, self.outcomes
        )
        if not predictions:
            raise ValueError("no buy trades with matching outcomes found")

        return log_loss(predictions, outcome_vals)

    def to_csv(self, path: str, what: str = "equity") -> None:
        """Export results to CSV.

        Args:
            path: File path to write.
            what: "equity" for equity curve, "trades" for trade log.
        """
        with open(path, "w", newline="", encoding="utf-8") as f:
            writer = csv.writer(f)
            if what == "equity":
                writer.writerow(["timestamp", "equity"])
                for ts, eq in self.equity_curve:
                    writer.writerow([ts, eq])
            elif what == "trades":
                writer.writerow([
                    "timestamp", "market_id", "order_side", "price", "size", "fee",
                ])
                for fill in self.trades:
                    writer.writerow([
                        getattr(fill, "timestamp", 0.0),
                        getattr(fill, "market_id", ""),
                        str(getattr(fill, "order_side", "")),
                        getattr(fill, "price", 0.0),
                        getattr(fill, "size", 0.0),
                        getattr(fill, "fee", 0.0) or 0.0,
                    ])
