"""Accesso Deribit MAINNET in SOLA LETTURA (via Cerbero MCP) + costruttore ordini deterministico.

Serve lo SHADOW MODE di TP01 (`scripts/live/live_trend.py`): legge prezzi / conto / posizioni REALI
dal mainnet (token `.env.mainnet`) e costruisce gli ordini di ribilancio **senza inviarli**. Qui NON
esiste alcun metodo di trading — by design: l'unica via per piazzare ordini sara' un modulo separato,
abilitato esplicitamente, dopo la validazione shadow + micro-test.

Disciplina del progetto: **testnet FUORI** (feed farlocco, causa del reset v2.0.0). Solo mainnet reale,
e in questa fase solo in lettura. I contratti sono ristretti a BTC/ETH-PERPETUAL (inverse: `amount`
in USD notional, step verificato su Deribit: BTC $10, ETH $1).
"""
from __future__ import annotations

import os
from decimal import Decimal
from pathlib import Path

import requests

PROJECT_ROOT = Path(__file__).resolve().parents[2]
BASE_URL = os.environ.get("CERBERO_BASE_URL", "https://cerbero-mcp.tielogic.xyz")
TIMEOUT = 15

# Inverse perp: amount = USD notional, step in USD, settle = base-coin (BTC/ETH).
# Linear USDC perp: amount = base-coin (BTC/ETH), step in base-coin, settle = USDC (margine USDC).
# NB il conto reale e' USDC -> gli strumenti ESEGUIBILI sono i LINEARI _USDC-PERPETUAL.
_CONTRACT = {
    "BTC-PERPETUAL": {"min": 10.0, "step": 10.0, "tick": 0.5, "settle": "BTC"},
    "ETH-PERPETUAL": {"min": 1.0, "step": 1.0, "tick": 0.05, "settle": "ETH"},
    "BTC_USDC-PERPETUAL": {"min": 0.0001, "step": 0.0001, "tick": 0.5, "settle": "USDC", "linear": True},
    "ETH_USDC-PERPETUAL": {"min": 0.001, "step": 0.001, "tick": 0.05, "settle": "USDC", "linear": True},
}
# Il conto reale e' USDC -> mappiamo gli asset sui perp LINEARI USDC (gli unici eseguibili qui).
INSTRUMENT = {"BTC": "BTC_USDC-PERPETUAL", "ETH": "ETH_USDC-PERPETUAL"}
DISASTER_LABEL = "tp01-disaster"   # label dei bracket disaster-SL (per ritrovarli/gestirli/mostrarli)


# ----------------------------- costruzione ordini (pura, testabile, NIENTE rete) -----------------------------

def _quantize_step(value: float, step: float, mn: float) -> float:
    """Arrotonda al multiplo di `step` (Decimal, niente artefatti float), clampa al minimo."""
    n = round(value / step)
    return float(max(n * Decimal(str(step)), Decimal(str(mn))))


def notional_to_amount(instrument: str, notional_usd: float, price: float | None = None) -> float:
    """USD notional -> `amount` Deribit, arrotondato allo step e clampato al minimo. Ritorna 0.0 se
    sotto mezzo step. INVERSE: amount in USD (price ignorato). LINEAR USDC: amount in base-coin
    (units = notional/price -> serve il `price`; senza price ritorna 0.0)."""
    spec = _CONTRACT[instrument]
    step, mn = spec["step"], spec["min"]
    if spec.get("linear"):
        if not price:
            return 0.0
        units = abs(notional_usd) / price
        if units < step / 2:
            return 0.0
        return _quantize_step(units, step, mn)
    if abs(notional_usd) < step / 2:
        return 0.0
    return _quantize_step(abs(notional_usd), step, mn)


def quantize_price(instrument: str, price: float) -> float:
    """Arrotonda il prezzo al tick dello strumento (per gli ordini stop/limit)."""
    tick = _CONTRACT[instrument].get("tick")
    if not tick or price <= 0:
        return price
    return float(round(price / tick) * Decimal(str(tick)))


def disaster_stop_price(instrument: str, mark: float, sl_pct: float, long: bool = True) -> float:
    """Prezzo del disaster-stop: ~sl_pct SOTTO il mark per un long (SOPRA per uno short), al tick."""
    raw = mark * (1 - sl_pct) if long else mark * (1 + sl_pct)
    return quantize_price(instrument, raw)


def target_notional_usd(target_fraction: float, weight: float, equity_usd: float) -> float:
    """Notional bersaglio (USD) di un asset = peso nel book * frazione-di-equity TP01 * equity.
    Coerente col paper trader (esposizione asset = WEIGHT * target * equity)."""
    return weight * target_fraction * equity_usd


def build_rebalance_order(instrument: str, target_fraction: float, weight: float,
                          equity_usd: float, current_pos_usd: float, price: float | None = None) -> dict | None:
    """COSTRUISCE (non invia) l'ordine di ribilancio verso il target. Ritorna un dict-ordine o None
    se sotto-soglia. Long-only TP01 -> target_notional >= 0; delta = target - posizione corrente.
    `price` (mark) serve a convertire il notional in base-coin per gli strumenti LINEARI USDC."""
    tgt = target_notional_usd(target_fraction, weight, equity_usd)
    delta = tgt - current_pos_usd
    amount = notional_to_amount(instrument, delta, price=price)
    if amount == 0.0:
        return None
    is_exit = abs(tgt) < 1e-9 and abs(current_pos_usd) > 0
    return dict(
        instrument=instrument,
        side="buy" if delta > 0 else "sell",
        amount=amount,
        type="market",
        reduce_only=is_exit,
        target_notional=round(tgt, 2),
        current_notional=round(current_pos_usd, 2),
        delta_notional=round(delta, 2),
    )


# ----------------------------- lettura mainnet (Cerbero MCP) — SOLA LETTURA -----------------------------

def _load_mainnet_token() -> tuple[str, str]:
    """Legge CERBERO_TOKEN (mainnet) + bot-tag da .env.mainnet. Il token NON viene mai stampato."""
    env: dict[str, str] = {}
    for ln in (PROJECT_ROOT / ".env.mainnet").read_text().splitlines():
        ln = ln.strip()
        if ln and not ln.startswith("#") and "=" in ln:
            k, v = ln.split("=", 1)
            env[k] = v.strip()
    if "CERBERO_TOKEN" not in env:
        raise RuntimeError("CERBERO_TOKEN assente in .env.mainnet")
    return env["CERBERO_TOKEN"], env.get("CERBERO_BOT_TAG", "pythagoras-shadow")


class DeribitRead:
    """Accesso Deribit mainnet in SOLA LETTURA via Cerbero MCP. Nessun metodo di trading (by design)."""

    def __init__(self) -> None:
        self._token, self._tag = _load_mainnet_token()

    def _post(self, path: str, payload: dict) -> dict | list:
        r = requests.post(
            f"{BASE_URL}{path}",
            headers={"Authorization": f"Bearer {self._token}", "X-Bot-Tag": self._tag,
                     "Content-Type": "application/json"},
            json=payload, timeout=TIMEOUT,
        )
        r.raise_for_status()
        return r.json()

    @staticmethod
    def _unwrap(resp: dict | list) -> dict | list:
        return resp.get("result", resp) if isinstance(resp, dict) else resp

    def ticker(self, instrument: str) -> dict:
        return self._unwrap(self._post("/mcp-deribit/tools/get_ticker", {"instrument": instrument})) or {}

    def mark_price(self, instrument: str) -> float:
        t = self.ticker(instrument)
        for k in ("mark_price", "index_price", "last_price", "last"):
            v = t.get(k)
            if v:
                return float(v)
        raise ValueError(f"prezzo assente nel ticker {instrument}: chiavi={list(t)[:8]}")

    def account_summary(self, currency: str) -> dict:
        return self._unwrap(self._post("/mcp-deribit/tools/get_account_summary", {"currency": currency})) or {}

    def positions(self, currency: str) -> list[dict]:
        out = self._unwrap(self._post("/mcp-deribit/tools/get_positions", {"currency": currency}))
        if isinstance(out, list):
            return out
        return out.get("positions", []) if isinstance(out, dict) else []

    def position_usd(self, instrument: str) -> float:
        """Size netta (USD notional, segno = direzione) della posizione su `instrument`. 0 se flat."""
        cur = _CONTRACT[instrument]["settle"]
        for p in self.positions(cur):
            if p.get("instrument_name") == instrument or p.get("instrument") == instrument:
                return float(p.get("size") or p.get("size_currency") or 0.0)
        return 0.0

    def trade_history(self, instrument: str, limit: int = 20) -> list[dict]:
        """Trade REALMENTE eseguiti sul conto per `instrument` (fonte autorevole fee/fill)."""
        out = self._unwrap(self._post("/mcp-deribit/tools/get_trade_history",
                                      {"limit": limit, "instrument_name": instrument}))
        return out if isinstance(out, list) else (out.get("trades", []) if isinstance(out, dict) else [])

    def open_orders(self, instrument: str) -> list[dict]:
        """Ordini APERTI su `instrument` (limit resting + trigger). Deribit puo' omettere i trigger
        untriggered da type='all' -> interroga anche 'trigger_all' e fa merge per order_id."""
        cur = _CONTRACT[instrument]["settle"]
        seen: dict = {}
        for typ in ("all", "trigger_all"):
            try:
                out = self._unwrap(self._post("/mcp-deribit/tools/get_open_orders",
                                              {"currency": cur, "type": typ}))
                lst = out if isinstance(out, list) else (out.get("orders", []) if isinstance(out, dict) else [])
                for o in lst:
                    if o.get("instrument_name") == instrument or o.get("instrument") == instrument:
                        seen[o.get("order_id")] = o
            except Exception:
                pass
        return list(seen.values())
