"""
V11 — Executor: taker orders para momentum.

Live mode:
  - GTC aggressive limit (al ask o above) — FOK no siempre soportado
  - Verificación de fill vía positions API
  - Balance check antes de operar
  - Cancel de órdenes pendientes si no se llenan
"""
import os, time
import requests as _requests
from .config import Config, HOST, CHAIN_ID, MIN_SHARES, PAPER_SLIPPAGE, snap_price

from py_clob_client_v2 import ClobClient, Side
from py_clob_client_v2 import BalanceAllowanceParams, AssetType
from py_clob_client_v2.clob_types import ApiCreds, OrderArgsV2

# pUSD token on Polygon (Polymarket's native stablecoin)
PUSD_TOKEN   = "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb"
POLYGON_RPC  = "https://polygon-bor-rpc.publicnode.com"

def _pusd_balance(address: str) -> float:
    """Read pUSD balance on-chain via Polygon RPC (decimals=6)."""
    data = "0x70a08231" + address[2:].lower().zfill(64)
    try:
        r = _requests.post(POLYGON_RPC, json={
            "jsonrpc": "2.0", "method": "eth_call",
            "params": [{"to": PUSD_TOKEN, "data": data}, "latest"], "id": 1,
        }, timeout=8)
        raw = r.json().get("result", "0x0")
        return int(raw, 16) / 1e6 if raw and raw != "0x" else 0.0
    except Exception:
        return 0.0


class Executor:
    def __init__(self, cfg: Config):
        self.cfg    = cfg
        self.client = None
        self._balance_cache = 0.0
        self._balance_ts    = 0.0

        if cfg.live:
            pk    = os.getenv("PK", "").strip().replace("0x", "")
            proxy = os.getenv("POLY_PROXY", "").strip()
            creds = ApiCreds(
                api_key=os.getenv("POLY_API_KEY"),
                api_secret=os.getenv("POLY_API_SECRET"),
                api_passphrase=os.getenv("POLY_API_PASSPHRASE"),
            )
            self.client = ClobClient(
                host=HOST, chain_id=CHAIN_ID, key=pk,
                creds=creds, signature_type=2, funder=proxy,
            )
            bal = self.get_balance()
            print(f"[*] Executor TAKER LIVE [{cfg.asset.upper()}] | "
                  f"proxy={proxy} | balance=${bal:.4f}")
        else:
            print(f"[*] Executor TAKER PAPER [{cfg.asset.upper()}]")

    # -- Balance --------------------------------------------------------------

    def get_balance(self, use_cache: bool = False) -> float:
        if not self.cfg.live or not self.client:
            return 9999.0
        if use_cache and (time.time() - self._balance_ts) < 30:
            return self._balance_cache
        proxy = os.getenv("POLY_PROXY", "").strip()
        bal = _pusd_balance(proxy) if proxy else 0.0
        self._balance_cache = bal
        self._balance_ts = time.time()
        return bal

    # -- Taker order ----------------------------------------------------------

    def place_taker_order(self, token_id: str, price: float, size: float,
                          label: str) -> tuple[bool, str, float]:
        """
        Place a taker BUY order at the ask price.
        Returns (success, order_id, fill_price).
        Paper: simulates fill at ask + slippage.
        Live: GTC aggressive limit, then verify fill via positions.
        """
        price = snap_price(price)
        size  = max(round(size, 2), MIN_SHARES)

        if not self.cfg.live or not self.client:
            fill_price = snap_price(price + PAPER_SLIPPAGE)
            oid = f"paper_{int(time.time())}_{label}"
            print(f"  [PAPER TAKE] {label} @ {fill_price:.2f} (ask={price:.2f}) "
                  f"x {size:.1f} sh = ${fill_price * size:.2f}")
            return True, oid, fill_price

        # Live: check balance first
        cost = price * size
        bal = self.get_balance()
        if bal < cost * 1.05:  # 5% margen
            print(f"  [TAKE SKIP] {label}: balance ${bal:.2f} < "
                  f"cost ${cost:.2f} + margen")
            return False, "", 0.0

        # Precio agresivo: subir 2 ticks del ask para asegurar fill
        aggressive_price = snap_price(price + 0.02)

        try:
            args = OrderArgsV2(
                token_id=token_id,
                price=aggressive_price,
                size=size,
                side="BUY",
            )
            resp = self.client.create_and_post_order(
                order_args=args,
                order_type="GTC",
            )

            success = resp.get("success", False)
            oid     = resp.get("orderID") or resp.get("order_id") or ""
            err_msg = resp.get("errorMsg") or resp.get("error_msg") or ""
            status  = (resp.get("status") or "").lower()

            if success or status in ("live", "open", "matched", "filled"):
                print(f"  [TAKE SENT] {label} @ {aggressive_price:.2f} "
                      f"x {size:.1f} sh | oid={str(oid)[:14]}...")
                # Precio real del fill se confirma después via positions
                return True, str(oid), aggressive_price
            else:
                print(f"  [TAKE FAIL] {label}: {err_msg or status or resp}")
                return False, "", 0.0

        except Exception as e:
            print(f"  [TAKE ERR] {label}: {str(e)[:80]}")
            return False, "", 0.0

    # -- Fill verification ----------------------------------------------------

    def verify_fill(self, session, token_id: str) -> dict | None:
        """Check if we have a position for this token via data API.
        Returns {"size": float, "avg_price": float} or None.
        """
        if not self.cfg.live or not self.client:
            return None
        proxy = os.getenv("POLY_PROXY", "").strip()
        if not proxy:
            return None
        try:
            url = (f"https://data-api.polymarket.com/positions"
                   f"?user={proxy}&sizeThreshold=0.01")
            r = session.get(url, timeout=8)
            r.raise_for_status()
            for p in r.json():
                tid = str(p.get("asset") or p.get("tokenId") or "")
                if tid != token_id:
                    continue
                sz = float(p.get("size") or 0)
                if sz <= 0:
                    continue
                avg = float(p.get("avgPrice") or p.get("avg_price") or 0)
                if avg == 0 and sz > 0:
                    initial = float(p.get("initialValue") or 0)
                    if initial > 0:
                        avg = round(initial / sz, 6)
                return {"size": sz, "avg_price": avg}
        except Exception as e:
            print(f"  [verify] error: {e}")
        return None

    # -- Cancel ---------------------------------------------------------------

    def cancel_order(self, order_id: str) -> bool:
        if not self.cfg.live or not self.client:
            return True
        try:
            resp = self.client.cancel_order(order_id)
            if isinstance(resp, str):
                return "canceled" in resp.lower() or "not found" in resp.lower()
            if isinstance(resp, dict):
                cancelled = resp.get("canceled", False) or resp.get("success", False)
                if not cancelled:
                    if "not found" in str(resp).lower():
                        return True
                return cancelled
            return True
        except Exception as e:
            err = str(e).lower()
            if "not found" in err or "already" in err:
                return True
            print(f"  [CANCEL ERR] {order_id[:14]}: {str(e)[:60]}")
            return False

    def cancel_all(self) -> int:
        if not self.cfg.live or not self.client:
            return 0
        try:
            resp = self.client.cancel_all()
            cancelled = resp.get("canceled", []) if isinstance(resp, dict) else []
            n = len(cancelled) if isinstance(cancelled, list) else 0
            if n:
                print(f"  [CANCEL ALL] {n} ordenes canceladas")
            return n
        except Exception as e:
            print(f"  [CANCEL ALL ERR] {str(e)[:60]}")
            return 0
