from __future__ import annotations

from decimal import Decimal
from typing import Any, Iterable

from src.execution.cost_model import CostModel, TradeCostInput
from src.hyperliquid.market_data import _impact_pct


def _d(value: Any) -> Decimal:
    return Decimal(str(value))


def _side_result(levels: list[dict[str, Any]], *, notional: Decimal, mid: Decimal, side: str, half_spread_pct: Decimal, expected_move_pct: Decimal, cost_model: CostModel) -> dict[str, Any]:
    impact = _impact_pct(levels, target_notional=notional, mid=mid, side=side)
    if impact is None:
        return {"depth_sufficient": False, "impact_pct": None, "roundtrip_cost_usd": None, "roundtrip_cost_pct": None, "cost_allowed": False}
    cost = cost_model.estimate(TradeCostInput(notional_usd=notional, expected_move_pct=expected_move_pct, half_spread_pct=half_spread_pct, depth_penalty_pct=impact))
    return {
        "depth_sufficient": True,
        "impact_pct": str(impact),
        "roundtrip_cost_usd": str(cost.roundtrip_cost_usd),
        "roundtrip_cost_pct": str(cost.roundtrip_cost_pct),
        "cost_allowed": cost.allowed,
    }


def analyze_size_curve(book: dict[str, Any], *, notionals: Iterable[Decimal], expected_move_pct: Decimal, risk_budget_usd: Decimal, stop_distance_pct: Decimal, max_impact_pct: Decimal = Decimal("0.05"), cost_model: CostModel | None = None) -> dict[str, Any]:
    levels = book.get("levels") or [[], []]
    bids = list(levels[0]) if len(levels) > 0 else []
    asks = list(levels[1]) if len(levels) > 1 else []
    if not bids or not asks:
        raise ValueError("two-sided L2 book required")
    best_bid = _d(bids[0]["px"])
    best_ask = _d(asks[0]["px"])
    if best_bid <= 0 or best_ask <= best_bid:
        raise ValueError("valid crossed-free L2 book required")
    mid = (best_bid + best_ask) / Decimal("2")
    spread_pct = (best_ask - best_bid) / mid * Decimal("100")
    half_spread_pct = spread_pct / Decimal("2")
    model = cost_model or CostModel()
    rows: list[dict[str, Any]] = []
    recommended: list[Decimal] = []
    for raw_notional in notionals:
        notional = _d(raw_notional)
        risk_usd = notional * stop_distance_pct / Decimal("100")
        buy = _side_result(asks, notional=notional, mid=mid, side="buy", half_spread_pct=half_spread_pct, expected_move_pct=expected_move_pct, cost_model=model)
        sell = _side_result(bids, notional=notional, mid=mid, side="sell", half_spread_pct=half_spread_pct, expected_move_pct=expected_move_pct, cost_model=model)
        risk_allowed = risk_usd <= risk_budget_usd
        impacts_ok = all(side["depth_sufficient"] and _d(side["impact_pct"]) <= max_impact_pct for side in (buy, sell))
        cost_allowed = bool(buy["cost_allowed"] and sell["cost_allowed"] and impacts_ok)
        row = {"notional_usd": str(notional), "risk_usd": str(risk_usd), "risk_allowed": risk_allowed, "cost_allowed": cost_allowed, "buy": buy, "sell": sell}
        rows.append(row)
        if risk_allowed and cost_allowed:
            recommended.append(notional)
    return {
        "schema_version": "size_impact_curve.v1",
        "mid_price": str(mid),
        "spread_pct": str(spread_pct),
        "expected_move_pct": str(expected_move_pct),
        "risk_budget_usd": str(risk_budget_usd),
        "stop_distance_pct": str(stop_distance_pct),
        "max_impact_pct": str(max_impact_pct),
        "rows": rows,
        "max_recommended_notional_usd": str(max(recommended)) if recommended else None,
        "read_only": True,
        "paper_research_only": True,
        "live_order_allowed": False,
        "mainnet_signed_action": False,
    }
