"""Pure paper-book math: target positions from weights+capital, trade diff, mark-to-market. No I/O."""
from __future__ import annotations
from dataclasses import dataclass, field


@dataclass(frozen=True)
class Position:
    sleeve: str
    symbol: str
    qty: float
    entry_price: float
    entry_date: str


@dataclass(frozen=True)
class Trade:
    ts: str
    sleeve: str
    symbol: str
    side: str   # BUY | SELL
    qty: float
    price: float
    reason: str


@dataclass(frozen=True)
class MTM:
    unrealized: float
    per_symbol: dict[str, float] = field(default_factory=dict)


def target_positions(sleeve: str, sleeve_weight: float, capital: float,
                     symbol_weights: dict[str, float], prices: dict[str, float], date: str) -> list[Position]:
    notional = sleeve_weight * capital
    out: list[Position] = []
    for sym, w in symbol_weights.items():
        px = prices.get(sym)
        if px is None or px <= 0:
            continue
        qty = (w * notional) / px
        if qty == 0.0:
            continue
        out.append(Position(sleeve, sym, qty, px, date))
    return out


def diff_trades(prior: list[Position], target: list[Position], ts: str, reason: str = "rebalance") -> list[Trade]:
    pri = {(p.sleeve, p.symbol): p for p in prior}
    tgt = {(p.sleeve, p.symbol): p for p in target}
    trades: list[Trade] = []
    for key in sorted(set(pri) | set(tgt)):
        sleeve, sym = key
        pq = pri[key].qty if key in pri else 0.0
        tq = tgt[key].qty if key in tgt else 0.0
        d = tq - pq
        if abs(d) < 1e-9:
            continue
        px = (tgt[key].entry_price if key in tgt else pri[key].entry_price)
        trades.append(Trade(ts, sleeve, sym, "BUY" if d > 0 else "SELL", abs(d), px, reason))
    return trades


def realized_delta(prior: list[Position], target: list[Position],
                   prices: dict[str, float]) -> float:
    """Realized PnL booked this rebalance: for every (sleeve, symbol) whose absolute exposure shrinks or
    flips sign, the closed quantity is marked from the prior entry price to the new day's exit price.

    closed_qty × (exit_price − prior_entry_price), summed. Average/entry-price basis (positions carry a
    single entry_price). Exit price is the new day's price (`prices`); a symbol with no available price
    books 0 realized (mark unknown — never invent a fill). A partial reduction closes the reduced qty; a
    full exit closes the whole prior lot; a sign flip closes the whole prior lot then opens the new side."""
    pri = {(p.sleeve, p.symbol): p for p in prior}
    tgt = {(p.sleeve, p.symbol): p for p in target}
    total = 0.0
    for key, pp in pri.items():
        pq = pp.qty
        if pq == 0.0:
            continue
        tp = tgt.get(key)
        tq = tp.qty if tp is not None else 0.0
        exit_px = prices.get(pp.symbol)
        if exit_px is None:
            continue                                   # no mark -> book nothing
        same_sign = (pq > 0) == (tq > 0)
        if tq != 0.0 and same_sign and abs(tq) >= abs(pq):
            continue                                   # added to the position — nothing closed
        # sign flip closes the whole prior lot; otherwise close the reduced (or fully-exited, tq==0) qty.
        closed = abs(pq) if (tq != 0.0 and not same_sign) else abs(pq) - abs(tq)
        signed_closed = closed if pq > 0 else -closed  # direction of the prior lot
        total += signed_closed * (exit_px - pp.entry_price)
    return total


def mark_to_market(positions: list[Position], prices: dict[str, float]) -> MTM:
    per: dict[str, float] = {}
    total = 0.0
    for p in positions:
        px = prices.get(p.symbol, p.entry_price)   # unknown -> entry (0 pnl)
        pnl = p.qty * (px - p.entry_price)
        per[p.symbol] = per.get(p.symbol, 0.0) + pnl
        total += pnl
    return MTM(unrealized=total, per_symbol=per)
