from fastapi import APIRouter
from pydantic import BaseModel
from typing import Optional, List
import yfinance as yf
import numpy as np
import pandas as pd
from datetime import datetime
from services.options_pricer import black_scholes

router = APIRouter(prefix="/api/backtest", tags=["backtest"])


class BacktestRequest(BaseModel):
    symbol: str
    start_date: str
    end_date: str
    strategy: str  # "long_call" | "long_put" | "bull_call_spread" | "bear_put_spread" | "straddle"
    strike_offset_pct: float = 0.05  # e.g. 5% OTM
    expiry_days: int = 90
    capital: float = 1000.0
    geo_filter: Optional[str] = None  # optional pattern id to filter


@router.post("/run")
def run_backtest(req: BacktestRequest):
    try:
        ticker = yf.Ticker(req.symbol)
        hist = ticker.history(start=req.start_date, end=req.end_date, interval="1d")
        if hist.empty or len(hist) < 20:
            return {"error": "Insufficient data for the period"}

        hist = hist.reset_index()
        returns = np.log(hist["Close"] / hist["Close"].shift(1)).dropna()

        trades = []
        equity = [req.capital]
        capital = req.capital
        r = 0.05
        T_open = req.expiry_days / 365

        step = max(1, req.expiry_days // 3)
        for i in range(0, len(hist) - req.expiry_days, step):
            row = hist.iloc[i]
            S = float(row["Close"])
            date_str = str(row["Date"])[:10]

            sigma_window = returns.iloc[max(0, i - 30):i]
            if len(sigma_window) < 5:
                continue
            sigma = float(sigma_window.std() * np.sqrt(252))
            if sigma < 0.01:
                sigma = 0.20

            if req.strategy in ["long_call", "bull_call_spread"]:
                K = S * (1 + req.strike_offset_pct)
            else:
                K = S * (1 - req.strike_offset_pct)

            result = black_scholes(S, K, T_open, r, sigma, "call" if "call" in req.strategy else "put")
            premium = result["price"]
            contracts = max(1, int((capital * 0.1) / (premium * 100)))
            cost = contracts * premium * 100

            expiry_idx = min(i + req.expiry_days, len(hist) - 1)
            S_expiry = float(hist.iloc[expiry_idx]["Close"])
            date_expiry = str(hist.iloc[expiry_idx]["Date"])[:10]

            if req.strategy in ["long_call", "bull_call_spread"]:
                intrinsic = max(0, S_expiry - K)
            else:
                intrinsic = max(0, K - S_expiry)

            pnl = (intrinsic - premium) * contracts * 100
            capital += pnl
            equity.append(round(capital, 2))

            trades.append({
                "entry_date": date_str,
                "exit_date": date_expiry,
                "strategy": req.strategy,
                "S_entry": round(S, 2),
                "K": round(K, 2),
                "premium": round(premium, 4),
                "contracts": contracts,
                "cost": round(cost, 2),
                "S_expiry": round(S_expiry, 2),
                "intrinsic": round(intrinsic, 4),
                "pnl": round(pnl, 2),
                "capital": round(capital, 2),
            })

        if not trades:
            return {"error": "No trades generated"}

        wins = [t for t in trades if t["pnl"] > 0]
        losses = [t for t in trades if t["pnl"] <= 0]
        total_pnl = sum(t["pnl"] for t in trades)
        gross_profit = sum(t["pnl"] for t in wins) if wins else 0
        gross_loss = abs(sum(t["pnl"] for t in losses)) if losses else 1

        eq = np.array(equity)
        peak = np.maximum.accumulate(eq)
        drawdown = (eq - peak) / peak
        max_dd = float(drawdown.min()) * 100

        equity_curve = [{"index": i, "capital": v} for i, v in enumerate(equity)]

        return {
            "symbol": req.symbol,
            "strategy": req.strategy,
            "period": f"{req.start_date} → {req.end_date}",
            "total_trades": len(trades),
            "wins": len(wins),
            "losses": len(losses),
            "win_rate": round(len(wins) / len(trades) * 100, 1) if trades else 0,
            "total_pnl": round(total_pnl, 2),
            "total_return_pct": round((capital - req.capital) / req.capital * 100, 2),
            "max_drawdown_pct": round(max_dd, 2),
            "profit_factor": round(gross_profit / gross_loss, 2) if gross_loss else 0,
            "final_capital": round(capital, 2),
            "equity_curve": equity_curve,
            "trades": trades[-20:],
        }

    except Exception as e:
        return {"error": str(e)}
