"""Pure liquidity-aware order planner. For each per-symbol dollar delta, produce ONE sized PlannedOrder:
capped by participation×ADV and order-book depth, rounded to exchange filters, dust/min-notional skipped,
priced maker-at-touch with an arrival-mid abort price, and flagged reduce_only when it reduces a position.
No I/O, no state — the orchestrator runs the maker→IOC loop using these plans."""
from __future__ import annotations

from dataclasses import dataclass

from fxhnt.domain.execution.rounding import meets_min_notional, round_price, round_qty
from fxhnt.ports.crypto_exchange import OrderBook, SymbolFilter


@dataclass(frozen=True, slots=True)
class RouteInput:
    symbol: str
    delta_usd: float          # signed target change in USD notional (+ = buy, - = sell)
    current_qty: float        # signed current base position
    filt: SymbolFilter
    book: OrderBook
    adv_usd: float            # average daily volume in USD (capacity)
    participation_rate: float
    depth_cap: float
    max_slippage_bps: float


@dataclass(frozen=True, slots=True)
class PlannedOrder:
    symbol: str
    side: str                 # "BUY" | "SELL"
    qty: float                # rounded base qty (>0)
    price: float              # maker limit @ touch
    abort_price: float        # IOC cross aborts beyond this (arrival mid ± max_slippage)
    reduce_only: bool
    capped: bool              # size was limited by participation/depth
    skip_reason: str | None   # None = actionable; else "dust"/"min_notional"/"no_book"


def plan_orders(inputs: list[RouteInput]) -> list[PlannedOrder]:
    return [_plan_one(i) for i in inputs]


def _plan_one(i: RouteInput) -> PlannedOrder:
    side = "BUY" if i.delta_usd > 0 else "SELL"
    mid = i.book.mid
    depth_qty = i.book.ask_qty if side == "BUY" else i.book.bid_qty
    if mid <= 0 or i.book.bid <= 0 or i.book.ask <= 0 or depth_qty <= 0:
        return PlannedOrder(i.symbol, side, 0.0, 0.0, 0.0, False, False, "no_book")

    target_usd = abs(i.delta_usd)
    # round_qty floors so qty can never overshoot |current_qty| when given use_usd/mid
    depth_usd = (i.book.bid_qty * i.book.bid) if side == "SELL" else (i.book.ask_qty * i.book.ask)
    cap_usd = min(i.participation_rate * i.adv_usd, i.depth_cap * depth_usd)
    capped = target_usd > cap_usd
    use_usd = min(target_usd, cap_usd)

    # Sizing uses MID deliberately: target_usd and current notional are both mark-to-mid
    # (the orchestrator values positions at mid), so qty = usd / mid keeps the realized
    # position's mid-value equal to the target weight.  Do NOT change to the limit price.
    qty = round_qty(use_usd / mid, i.filt)
    if qty <= 0:
        return PlannedOrder(i.symbol, side, 0.0, 0.0, 0.0, False, capped, "dust")
    if not meets_min_notional(qty, mid, i.filt):
        return PlannedOrder(i.symbol, side, 0.0, 0.0, 0.0, False, capped, "min_notional")

    price = round_price(i.book.bid if side == "BUY" else i.book.ask, i.filt)   # maker @ touch
    slip = i.max_slippage_bps / 1e4
    abort = round_price(mid * (1 + slip) if side == "BUY" else mid * (1 - slip), i.filt)
    reduce_only = (i.current_qty > 0 and side == "SELL" and qty <= i.current_qty) or \
                  (i.current_qty < 0 and side == "BUY" and qty <= abs(i.current_qty))
    return PlannedOrder(i.symbol, side, qty, price, abort, reduce_only, capped, None)
