"""
HYPERION — paper trading book.

Simulates fills at mid ± half-spread proxy, applies taker fees, tracks
MAE/MFE per position, enforces SL/TP, persists everything to Supabase
(hyperion_trades). Stateless across restarts: open positions reload from DB.
"""
from __future__ import annotations
from datetime import datetime, timezone

TAKER_FEE_BPS = 4.5     # HL taker ~0.045%; maker rebates modeled in P3
SLIP_BPS = 2.0          # entry/exit slippage assumption for paper


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


class PaperBook:
    def __init__(self, sb_get, sb_insert, sb_update, log=print):
        self.sb_get = sb_get
        self.sb_insert = sb_insert
        self.sb_update = sb_update
        self.log = log
        self.open: dict[int, dict] = {}   # trade_id -> row
        self.reload()

    # ---------- persistence ----------
    def reload(self) -> None:
        rows = self.sb_get("hyperion_trades",
                           {"status": "eq.open", "mode": "eq.paper", "select": "*"})
        self.open = {r["id"]: r for r in rows}

    def count(self) -> int:
        return len(self.open)

    def has(self, coin: str) -> bool:
        return any(r["coin"] == coin for r in self.open.values())

    # ---------- lifecycle ----------
    def enter(self, coin: str, dex: str, side: str, size: float, px: float,
              sl_px: float, tp_px: float, strategy: str, regime: str, reason: str) -> None:
        slip = px * SLIP_BPS / 10000 * (1 if side == "long" else -1)
        fill = px + slip
        fee = abs(size * fill) * TAKER_FEE_BPS / 10000
        row = {
            "mode": "paper", "dex": dex, "coin": coin, "side": side,
            "size": size, "entry_px": fill, "entry_ts": now_iso(),
            "status": "open", "strategy": strategy, "regime": regime,
            "sl_px": sl_px, "tp_px": tp_px, "fees": fee,
            "mae": 0, "mfe": 0,
            "slippage_bps": SLIP_BPS,
            "raw": {"reason": reason},
        }
        out = self.sb_insert("hyperion_trades", row, ret=True)
        if out:
            self.open[out[0]["id"]] = out[0]
        self.log(f"  PAPER ENTER {side} {coin} sz {size:.6g} @ {fill:.6g} "
                 f"[{strategy}/{regime}] sl {sl_px:.6g} tp {tp_px:.6g}")

    def close(self, tid: int, px: float, why: str) -> float:
        r = self.open.get(tid)
        if not r:
            return 0.0
        side_mult = 1 if r["side"] == "long" else -1
        slip = px * SLIP_BPS / 10000 * (-1 if r["side"] == "long" else 1)
        fill = px + slip
        gross = (fill - float(r["entry_px"])) * side_mult * float(r["size"])
        fee = abs(float(r["size"]) * fill) * TAKER_FEE_BPS / 10000
        total_fees = float(r.get("fees") or 0) + fee
        pnl = gross - total_fees
        self.sb_update("hyperion_trades", {"id": f"eq.{tid}"}, {
            "status": "closed", "exit_px": fill, "exit_ts": now_iso(),
            "realized_pnl": pnl, "fees": total_fees,
            "raw": {**(r.get("raw") or {}), "close_reason": why}})
        del self.open[tid]
        self.log(f"  PAPER CLOSE {r['side']} {r['coin']} @ {fill:.6g} "
                 f"pnl {pnl:+.4f} ({why})")
        return pnl

    # ---------- per-tick management ----------
    def manage(self, mids: dict) -> list[float]:
        """Update MAE/MFE, fire SL/TP. Returns realized pnls of closes this tick."""
        realized = []
        for tid, r in list(self.open.items()):
            px = mids.get(r["coin"])
            if px is None:
                continue
            px = float(px)
            entry = float(r["entry_px"])
            side_mult = 1 if r["side"] == "long" else -1
            excursion = (px - entry) * side_mult / entry  # frac, + favorable
            mae = min(float(r.get("mae") or 0), excursion)
            mfe = max(float(r.get("mfe") or 0), excursion)
            if mae != r.get("mae") or mfe != r.get("mfe"):
                r["mae"], r["mfe"] = mae, mfe
                try:
                    self.sb_update("hyperion_trades", {"id": f"eq.{tid}"},
                                   {"mae": mae, "mfe": mfe})
                except Exception:
                    pass
            sl, tp = r.get("sl_px"), r.get("tp_px")
            if sl and ((r["side"] == "long" and px <= float(sl)) or
                       (r["side"] == "short" and px >= float(sl))):
                realized.append(self.close(tid, px, "sl"))
            elif tp and ((r["side"] == "long" and px >= float(tp)) or
                         (r["side"] == "short" and px <= float(tp))):
                realized.append(self.close(tid, px, "tp"))
        return realized

    def upnl(self, mids: dict) -> float:
        total = 0.0
        for r in self.open.values():
            px = mids.get(r["coin"])
            if px is None:
                continue
            side_mult = 1 if r["side"] == "long" else -1
            total += (float(px) - float(r["entry_px"])) * side_mult * float(r["size"])
        return total

    def set_sltp(self, coin: str, sl: float | None, tp: float | None) -> bool:
        for tid, r in self.open.items():
            if r["coin"] == coin:
                patch = {}
                if sl:
                    patch["sl_px"] = sl
                if tp:
                    patch["tp_px"] = tp
                if patch:
                    self.sb_update("hyperion_trades", {"id": f"eq.{tid}"}, patch)
                    r.update(patch)
                return True
        return False

    def close_coin(self, coin: str, mids: dict) -> bool:
        for tid, r in list(self.open.items()):
            if r["coin"] == coin and r["coin"] in mids:
                self.close(tid, float(mids[coin]), "manual")
                return True
        return False
