"""Paper Trading service — opens trades from approved signals, marks open
trades to market, and closes them on stop/target/manual exit. No real orders,
ever.

Each trade tracks two views of the same idea:
- **Shares**: entry/stop/target on the underlying (drives auto-close and
  validates the directional call).
- **Option**: the contract selected by the Options Agent, sized so total
  premium fits the per-trade risk budget, repriced with Black-Scholes as the
  underlying moves and time decays.
"""
import os
from datetime import datetime

from fastapi import HTTPException
from sqlalchemy.orm import Session

from .. import models
from ..config import settings
from . import position_mgmt
from .market_data import get_provider
from .options_pricing import reprice_premium, size_contracts


def _paper_slippage_bps() -> float:
    try:
        return float(os.getenv("PAPER_SLIPPAGE_BPS", "5"))
    except ValueError:
        return 5.0


def open_trade(db: Session, signal_id: int, quantity: int | None = None) -> models.PaperTrade:
    signal = db.get(models.Signal, signal_id)
    if not signal:
        raise HTTPException(404, "Signal not found")
    if signal.status != "approved":
        raise HTTPException(400, "Only approved signals can be paper-traded")
    qty = quantity or signal.position_size
    if qty <= 0:
        raise HTTPException(400, "Quantity must be positive")

    # Realistic fill: entry pays slippage in the adverse direction
    sign = 1 if signal.direction in ("CALL", "BUY") else -1
    fill = round(signal.entry * (1 + sign * _paper_slippage_bps() / 10_000), 2)

    trade = models.PaperTrade(
        signal_id=signal.id,
        symbol=signal.symbol,
        direction=signal.direction,
        quantity=qty,
        entry_price=fill,
        stop_loss=signal.stop_loss,
        initial_stop=signal.stop_loss,
        target=signal.target1,
        last_price=fill,
        status="open",
    )

    # Attach the selected option contract, sized to the risk budget
    contract = signal.option_contract or {}
    if contract.get("strike"):
        bid, ask = float(contract.get("bid", 0)), float(contract.get("ask", 0))
        premium = float(contract.get("mid") or 0) or round((bid + ask) / 2, 2)
        max_risk = settings.ACCOUNT_EQUITY * settings.MAX_RISK_PER_TRADE_PCT / 100
        contracts = size_contracts(premium, max_risk)
        if premium > 0 and contracts > 0:
            trade.instrument = "option"
            trade.option_contract = contract
            trade.option_entry_premium = premium
            trade.option_last_premium = premium
            trade.option_contracts = contracts
            trade.option_pnl = 0.0

    db.add(trade)
    db.commit()
    db.refresh(trade)
    return trade


def _is_long(direction: str) -> bool:
    return direction in ("CALL", "BUY")


def _unrealized(trade: models.PaperTrade, price: float) -> float:
    sign = 1 if _is_long(trade.direction) else -1
    return round((price - trade.entry_price) * sign * trade.quantity, 2)


def _track_excursions(trade: models.PaperTrade, price: float) -> None:
    """Prediction audit: record max favorable/adverse excursion in R."""
    sign = 1 if _is_long(trade.direction) else -1
    initial_stop = trade.initial_stop if trade.initial_stop is not None else trade.stop_loss
    risk = abs(trade.entry_price - initial_stop)
    if risk <= 0:
        return
    r_now = (price - trade.entry_price) * sign / risk
    trade.mfe_r = round(max(trade.mfe_r or 0.0, r_now), 2)
    trade.mae_r = round(min(trade.mae_r or 0.0, r_now), 2)


def _update_option_mark(trade: models.PaperTrade, underlying_price: float) -> None:
    if trade.instrument != "option" or not trade.option_contract:
        return
    premium_now = reprice_premium(
        contract=trade.option_contract,
        entry_premium=trade.option_entry_premium or 0.0,
        entry_underlying=trade.entry_price,
        entry_date=trade.opened_at,
        current_underlying=underlying_price,
    )
    trade.option_last_premium = premium_now
    trade.option_pnl = round(
        (premium_now - (trade.option_entry_premium or 0.0)) * 100 * trade.option_contracts, 2
    )


def mark_to_market(db: Session) -> list[models.PaperTrade]:
    """Update open trades with latest prices; auto-close on stop/target.

    Returns the trades that were CLOSED during this call (for alerting).
    """
    provider = get_provider()
    now = datetime.utcnow()
    open_trades = db.query(models.PaperTrade).filter(models.PaperTrade.status == "open").all()
    closed_now: list[models.PaperTrade] = []
    for trade in open_trades:
        price = provider.get_quote(trade.symbol)["price"]
        trade.last_price = price
        trade.pnl = _unrealized(trade, price)
        _track_excursions(trade, price)
        _update_option_mark(trade, price)
        long = _is_long(trade.direction)
        sign = 1 if long else -1

        # Position management: trail / scale-out / time stop
        actions = position_mgmt.evaluate(trade, price, now)
        if actions["time_exit"]:
            trade.status = "closed"
            trade.exit_price = price
            trade.exit_reason = "time_stop"
            trade.closed_at = now
            closed_now.append(trade)
            continue
        if actions["new_stop"] is not None:
            trade.stop_loss = actions["new_stop"]
        if actions["scale_out_qty"]:
            half = actions["scale_out_qty"]
            banked = models.PaperTrade(
                signal_id=trade.signal_id,
                symbol=trade.symbol,
                direction=trade.direction,
                quantity=half,
                entry_price=trade.entry_price,
                stop_loss=trade.stop_loss,
                initial_stop=trade.initial_stop,
                target=trade.target,
                status="closed",
                exit_price=price,
                exit_reason="scale_out",
                pnl=round((price - trade.entry_price) * sign * half, 2),
                last_price=price,
                opened_at=trade.opened_at,
                closed_at=now,
            )
            db.add(banked)
            closed_now.append(banked)
            trade.quantity -= half
            trade.scaled_out = 1
            trade.pnl = _unrealized(trade, price)

        hit_stop = price <= trade.stop_loss if long else price >= trade.stop_loss
        hit_target = price >= trade.target if long else price <= trade.target
        if hit_stop or hit_target:
            trade.status = "closed"
            trade.exit_price = price
            trade.exit_reason = "stop_loss" if hit_stop else "target"
            trade.closed_at = now
            closed_now.append(trade)
    db.commit()
    return closed_now


def close_trade(db: Session, trade_id: int) -> models.PaperTrade:
    trade = db.get(models.PaperTrade, trade_id)
    if not trade:
        raise HTTPException(404, "Trade not found")
    if trade.status == "closed":
        raise HTTPException(400, "Trade already closed")
    price = get_provider().get_quote(trade.symbol)["price"]
    trade.last_price = price
    trade.exit_price = price
    trade.exit_reason = "manual"
    trade.pnl = _unrealized(trade, price)
    _track_excursions(trade, price)
    _update_option_mark(trade, price)
    trade.status = "closed"
    trade.closed_at = datetime.utcnow()
    db.commit()
    db.refresh(trade)
    return trade
