"""Strategy backtesting — walk-forward simulation.

For each symbol, the engine replays history bar by bar: at each day it runs
the Technical Analysis Agent on ONLY the bars known up to that day (no
look-ahead), and when conviction clears the threshold it simulates a trade
with the same rules the live pipeline uses (entry next open, stop at 1.5×ATR,
target at 2R, conservative stop-first fill, max-hold time exit).

Results are reported in R-multiples (profit relative to initial risk), which
is position-size independent and directly comparable to the risk rules.
"""
from ..agents.indicators import atr
from ..agents.technical import TechnicalAnalysisAgent
from .market_data import UNIVERSE, get_provider

DEFAULT_SYMBOLS = [s for s, _, _ in UNIVERSE[:12]]
WARMUP_BARS = 40          # bars needed before indicators are trustworthy
DEFAULT_HORIZON = 10      # max holding period in bars


def _simulate_symbol(bars: list[dict], min_score: float, horizon: int,
                     slippage_bps: float = 5.0, commission_per_share: float = 0.005) -> list[dict]:
    trades: list[dict] = []
    tech = TechnicalAnalysisAgent()
    slip = slippage_bps / 10_000
    i = WARMUP_BARS
    while i < len(bars) - 2:
        window = bars[: i + 1]
        result = tech.run({"history": window})
        if result.score >= min_score and result.direction:
            sign = 1 if result.direction == "CALL" else -1
            a = atr(window) or window[-1]["close"] * 0.02
            # Realistic fill: entry pays slippage in the adverse direction
            entry = bars[i + 1]["open"] * (1 + sign * slip)
            stop = entry - sign * 1.5 * a
            target = entry + sign * 3.0 * a  # 2R
            risk = abs(entry - stop)
            outcome, exit_price, exit_j = None, None, min(i + horizon, len(bars) - 1)

            for j in range(i + 1, min(i + 1 + horizon, len(bars))):
                b = bars[j]
                # Conservative: if both stop and target touch in a bar, assume stop
                if sign > 0:
                    if b["low"] <= stop:
                        outcome, exit_price, exit_j = "stop", stop, j
                        break
                    if b["high"] >= target:
                        outcome, exit_price, exit_j = "target", target, j
                        break
                else:
                    if b["high"] >= stop:
                        outcome, exit_price, exit_j = "stop", stop, j
                        break
                    if b["low"] <= target:
                        outcome, exit_price, exit_j = "target", target, j
                        break
            if outcome is None:
                outcome, exit_price = "time", bars[exit_j]["close"]

            # Exit also pays slippage; commissions charged both ways
            exit_fill = exit_price * (1 - sign * slip)
            costs = 2 * commission_per_share
            r = round(((exit_fill - entry) * sign - costs) / risk, 2) if risk else 0.0
            trades.append({
                "date": bars[i + 1]["date"],
                "direction": result.direction,
                "score": result.score,
                "entry": round(entry, 2),
                "exit": round(exit_fill, 2),
                "outcome": outcome,
                "r": r,
            })
            i = exit_j + 1  # one position per symbol at a time
        else:
            i += 1
    return trades


def _stats(trades: list[dict]) -> dict:
    if not trades:
        return {"trades": 0, "win_rate": 0.0, "avg_r": 0.0, "expectancy_r": 0.0,
                "profit_factor": 0.0, "total_r": 0.0, "max_drawdown_r": 0.0}
    wins = [t for t in trades if t["r"] > 0]
    gross_win = sum(t["r"] for t in wins)
    gross_loss = abs(sum(t["r"] for t in trades if t["r"] < 0))
    total_r = round(sum(t["r"] for t in trades), 2)

    # Max drawdown on the cumulative-R equity curve
    peak, dd, cum = 0.0, 0.0, 0.0
    for t in trades:
        cum += t["r"]
        peak = max(peak, cum)
        dd = max(dd, peak - cum)

    return {
        "trades": len(trades),
        "win_rate": round(len(wins) / len(trades) * 100, 1),
        "avg_r": round(total_r / len(trades), 2),
        "expectancy_r": round(total_r / len(trades), 2),
        "profit_factor": round(gross_win / gross_loss, 2) if gross_loss else round(gross_win, 2),
        "total_r": total_r,
        "max_drawdown_r": round(dd, 2),
    }


def run_backtest(symbols: list[str] | None = None, days: int = 180,
                 min_score: float = 60.0, horizon: int = DEFAULT_HORIZON,
                 slippage_bps: float = 5.0, commission_per_share: float = 0.005) -> dict:
    provider = get_provider()
    symbols = symbols or DEFAULT_SYMBOLS
    days = max(90, min(730, days))
    horizon = max(3, min(30, horizon))

    per_symbol: list[dict] = []
    all_trades: list[dict] = []
    for symbol in symbols:
        bars = provider.get_history(symbol, days)
        if len(bars) < WARMUP_BARS + 10:
            continue
        trades = _simulate_symbol(bars, min_score, horizon, slippage_bps, commission_per_share)
        for t in trades:
            t["symbol"] = symbol
        all_trades.extend(trades)
        per_symbol.append({"symbol": symbol, **_stats(trades)})

    all_trades.sort(key=lambda t: t["date"])
    equity, cum = [], 0.0
    for t in all_trades:
        cum = round(cum + t["r"], 2)
        equity.append(cum)

    return {
        "params": {"symbols": symbols, "days": days, "min_score": min_score, "horizon": horizon,
                   "slippage_bps": slippage_bps, "commission_per_share": commission_per_share},
        "overall": _stats(all_trades),
        "equity_curve_r": equity,
        "per_symbol": sorted(per_symbol, key=lambda s: s["total_r"], reverse=True),
        "recent_trades": all_trades[-25:][::-1],
        "note": (f"Walk-forward simulation, no look-ahead. Costs modeled: {slippage_bps}bps slippage "
                 f"each way + ${commission_per_share}/share commissions. R = profit relative to "
                 "initial risk. Mock data unless DATA_PROVIDER=live. "
                 "Past performance never guarantees future results."),
    }
