"""
V16 — Executor: GTC limit al ask + bump, espera 4s si queda live.

  - status=matched/filled → fill confirmado al precio límite
  - status=live/open      → esperar LIVE_ORDER_WAIT segundos, luego intentar cancelar:
      · si se cancela  → no hubo fill → no entrar
      · si ya no existe → ya se llenó durante la espera → entrar
  - Error                 → no entrar

Sin verify_fill, sin market orders, sin phantoms.
"""
import os, time
from .config import Config, HOST, CHAIN_ID, MIN_SHARES, PAPER_SLIPPAGE, snap_price

from py_clob_client_v2 import ClobClient
from py_clob_client_v2.clob_types import (
    ApiCreds, OrderArgsV2,
    BalanceAllowanceParams, AssetType, OrderPayload,
)

LIVE_ORDER_WAIT = 4  # segundos que esperamos un fill antes de cancelar


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()
            sig_type = int(os.getenv("POLY_SIG_TYPE", "2"))
            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=sig_type, 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
        try:
            r = self.client.get_balance_allowance(
                BalanceAllowanceParams(asset_type=AssetType.COLLATERAL)
            )
            bal = int(r.get("balance", 0)) / 1e6
        except Exception:
            bal = self._balance_cache
        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, str]:
        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, "matched"

        aggressive_price = snap_price(price + self.cfg.aggressive_bump)
        cost = aggressive_price * size

        bal = self.get_balance(use_cache=True)
        if bal < cost * 1.05:
            print(f"  [TAKE SKIP] {label}: balance ${bal:.2f} < cost ${cost:.2f}")
            return False, "", 0.0, ""

        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",
            )

            if isinstance(resp, str):
                resp = {"success": True, "orderID": resp}

            oid    = resp.get("orderID") or resp.get("order_id") or ""
            status = (resp.get("status") or "").lower()
            err    = resp.get("errorMsg") or resp.get("error_msg") or ""

            if status in ("matched", "filled"):
                taking = float(resp.get("takingAmount") or size)
                actual_size = taking if taking > 0 else size
                print(f"  [TAKE FILLED] {label} @ {aggressive_price:.2f} "
                      f"x {actual_size:.1f} sh | oid={str(oid)[:14]}...")
                return True, str(oid), aggressive_price, "matched"

            elif status in ("live", "open", "pending"):
                # Esperar LIVE_ORDER_WAIT segundos — Polymarket suele matchear en 1-3s
                bal_before = self.get_balance()
                print(f"  [TAKE WAITING] {label} @ {aggressive_price:.2f} "
                      f"x {size:.1f} sh — esperando {LIVE_ORDER_WAIT}s fill... "
                      f"oid={str(oid)[:14]}...")
                time.sleep(LIVE_ORDER_WAIT)

                # Doble verificación: intento de cancel + comparación de balance.
                # El CLOB a veces devuelve una orden filled en la lista "canceled",
                # así que usamos la caída de balance como fuente de verdad.
                self._cancel_check(oid)  # intentar cancelar (best effort)
                bal_after = self.get_balance()
                spent = round(bal_before - bal_after, 4)

                if spent >= cost * 0.5:
                    # Balance bajó lo suficiente → la orden se llenó
                    actual_price = snap_price(spent / size) if size > 0 else aggressive_price
                    print(f"  [TAKE DELAYED] {label} @ ~{actual_price:.2f} "
                          f"x {size:.1f} sh (balance -{spent:.2f}$ confirma fill)")
                    return True, str(oid), actual_price, "matched"
                else:
                    print(f"  [TAKE TIMEOUT] {label}: sin fill en {LIVE_ORDER_WAIT}s "
                          f"(balance delta={spent:.4f}$)")
                    return False, "", 0.0, ""

            else:
                print(f"  [TAKE FAIL] {label}: {err or status or resp}")
                return False, "", 0.0, ""

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

    def _cancel_check(self, order_id: str) -> bool:
        """Intenta cancelar la orden. Devuelve True si ya estaba filled (no se pudo cancelar),
        False si se canceló exitosamente (no hubo fill)."""
        if not order_id:
            return False
        try:
            resp = self.client.cancel_order(OrderPayload(orderID=order_id))
            if isinstance(resp, dict):
                cancelled = resp.get("canceled", []) or []
                if order_id in cancelled:
                    return False  # cancelada = no hubo fill
                # Si no está en canceled → ya estaba filled/not found
                return True
            # Respuesta no-dict inesperada: asumir filled para no perder el trade
            return True
        except Exception as e:
            err = str(e).lower()
            # "not found" o "already" significa que ya fue procesada (filled)
            if "not found" in err or "already" in err or "matched" in err:
                return True
            print(f"  [CANCEL CHECK ERR] {order_id[:14]}: {str(e)[:60]}")
            return False  # en caso de error desconocido, no asumir fill

    # -- 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(OrderPayload(orderID=order_id))
            if isinstance(resp, dict):
                cancelled = resp.get("canceled", [])
                if order_id in (cancelled or []):
                    return True
                nc = resp.get("not_canceled", {})
                return "not found" in str(nc).lower()
            return True
        except Exception as e:
            if "not found" in str(e).lower() or "already" in str(e).lower():
                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
