from __future__ import annotations

import os
from typing import Any, Optional

from ..common.cpz_ai import CPZAIClient
from ..data.client import DataClient
from ..execution.models import Account, Order, OrderReplaceRequest, OrderSubmitRequest, Position, PortfolioHistory
from ..execution.enums import OrderSide, OrderType, TimeInForce
from ..execution.router import BrokerRouter
from ..simons import SimonsClient
from .base import BaseClient


class _ExecutionNamespace:
    def __init__(self, router: BrokerRouter) -> None:
        self.router = router

    def use_broker(
        self, name: str, environment: str = "paper", account_id: Optional[str] = None
    ) -> None:
        self.router.use_broker(name, environment=environment, account_id=account_id)

    def set_risk_guard(
        self,
        max_order_value: float | None = None,
        max_order_qty: float | None = None,
        max_position_pct: float | None = None,
        max_notional_exposure: float | None = None,
        max_daily_loss: float | None = None,
        max_open_orders: int | None = None,
        blocked_symbols: list[str] | None = None,
        allowed_symbols: list[str] | None = None,
        cooldown_seconds: float | None = None,
        max_orders_per_minute: int | None = None,
    ) -> None:
        """Attach a pre-trade risk guard that validates every order.

        Any parameter set to ``None`` disables that particular check.
        Raises ``RiskGuardViolation`` when an order breaches a rule.
        """
        from ..execution.risk_guard import RiskGuard

        guard = RiskGuard(
            max_order_value=max_order_value,
            max_order_qty=max_order_qty,
            max_position_pct=max_position_pct,
            max_notional_exposure=max_notional_exposure,
            max_daily_loss=max_daily_loss,
            max_open_orders=max_open_orders,
            blocked_symbols=blocked_symbols,
            allowed_symbols=allowed_symbols,
            cooldown_seconds=cooldown_seconds,
            max_orders_per_minute=max_orders_per_minute,
        )
        self.router.set_risk_guard(guard)

    def get_account(self) -> Account:
        return self.router.get_account()

    def get_positions(self) -> list[Position]:
        return self.router.get_positions()

    def submit_order(self, req: OrderSubmitRequest) -> Order:
        return self.router.submit_order(req)

    def get_order(self, order_id: str) -> Order:
        return self.router.get_order(order_id)

    def cancel_order(self, order_id: str) -> Order:
        return self.router.cancel_order(order_id)

    def replace_order(self, order_id: str, req: OrderReplaceRequest) -> Order:
        return self.router.replace_order(order_id, req)

    def list_orders(
        self,
        status: Optional[str] = None,
        limit: int = 50,
        after: Optional[Any] = None,
        until: Optional[Any] = None,
        symbols: Optional[list[str]] = None,
    ) -> list[Order]:
        """List orders from the broker.

        Args:
            status: Filter by status - "open", "closed", or "all" (default: all)
            limit: Max number of orders to return (default: 50)
            after: Only return orders created after this datetime
            until: Only return orders created before this datetime
            symbols: Filter by symbols (e.g. ["BTC/USD", "NVDA"])
        """
        return self.router.list_orders(
            status=status, limit=limit, after=after, until=until, symbols=symbols,
        )

    def close_position(self, symbol: str) -> Order:
        """Close an open position for a symbol by liquidating all shares/units."""
        return self.router.close_position(symbol)

    def close_all_positions(self) -> list[Order]:
        """Close all open positions and cancel pending orders."""
        return self.router.close_all_positions()

    def get_portfolio_history(
        self, period: str = "1D", timeframe: str = "1H"
    ) -> PortfolioHistory:
        """Get historical portfolio equity and P&L.

        Args:
            period: Time period - "1D", "1W", "1M", "3M", "1A", etc.
            timeframe: Bar size - "1Min", "5Min", "15Min", "1H", "1D"
        """
        return self.router.get_portfolio_history(period=period, timeframe=timeframe)

    def deploy_to_engine(
        self,
        strategy_id: str,
        symbols: list[str] | None = None,
        strategy_type: str = "custom",
        params: dict | None = None,
        risk: dict | None = None,
    ):
        """Deploy a strategy to the Rust HFT engine for autonomous execution.

        Requires use_broker("hft_engine") to be called first.

        Args:
            strategy_id: CPZ platform strategy ID
            symbols: Symbols to trade (e.g., ["SPY", "QQQ"])
            strategy_type: Signal type (e.g., "momentum", "mean_reversion")
            params: Strategy parameters
            risk: Risk limits dict

        Returns:
            DeployStrategyResponse with success, executor_id, error
        """
        from ..execution.hft_engine.adapter import HFTEngineAdapter
        adapter = self.router.active_adapter()
        if not isinstance(adapter, HFTEngineAdapter):
            raise TypeError(
                "deploy_to_engine requires hft_engine broker. "
                "Call use_broker('hft_engine') first."
            )
        return adapter.deploy_strategy(
            strategy_id=strategy_id,
            strategy_type=strategy_type,
            params=params,
            symbols=symbols,
            risk=risk,
        )

    def stop_engine_strategy(self, strategy_id: str = ""):
        """Stop a strategy running on the HFT engine."""
        from ..execution.hft_engine.adapter import HFTEngineAdapter
        adapter = self.router.active_adapter()
        if not isinstance(adapter, HFTEngineAdapter):
            raise TypeError(
                "stop_engine_strategy requires hft_engine broker. "
                "Call use_broker('hft_engine') first."
            )
        return adapter.stop_strategy(strategy_id)

    def list_engine_executors(self) -> list:
        """List all active executors on the HFT engine."""
        from ..execution.hft_engine.adapter import HFTEngineAdapter
        adapter = self.router.active_adapter()
        if not isinstance(adapter, HFTEngineAdapter):
            raise TypeError(
                "list_engine_executors requires hft_engine broker. "
                "Call use_broker('hft_engine') first."
            )
        return adapter.list_executors()

    def get_engine_status(self):
        """Get HFT engine status including active executor count."""
        from ..execution.hft_engine.adapter import HFTEngineAdapter
        adapter = self.router.active_adapter()
        if not isinstance(adapter, HFTEngineAdapter):
            raise TypeError(
                "get_engine_status requires hft_engine broker. "
                "Call use_broker('hft_engine') first."
            )
        return adapter.get_engine_status()

    # Convenience: one-call order placement with CPZ-managed broker creds
    def order(
        self,
        *,
        symbol: str,
        qty: float | str | None = None,
        notional: float | str | None = None,
        side: str | OrderSide = "buy",
        order_type: str | OrderType = "market",
        time_in_force: str | TimeInForce = "DAY",
        limit_price: float | None = None,
        strategy_id: str | None = None,
    ) -> Order:
        # Validate strategy_id is provided (required for all orders)
        if not strategy_id:
            strategy_id = os.getenv("CPZ_AI_STRATEGY_ID") or os.getenv("CPZ_STRATEGY_ID")
        if not strategy_id:
            raise ValueError(
                "strategy_id is required for all orders. "
                "Provide it as a parameter or set CPZ_AI_STRATEGY_ID environment variable."
            )
        # Ensure a broker is selected; adapter will fetch creds from CPZAI
        # If no active broker, this will raise appropriately
        _active = self.router.active_selection()
        if _active is None:
            # Common ergonomic fallback: default to Alpaca paper if nothing selected
            self.router.use_broker("alpaca", environment="paper")

        side_enum = (
            side
            if isinstance(side, OrderSide)
            else (OrderSide.BUY if str(side).lower() == "buy" else OrderSide.SELL)
        )
        type_enum = (
            order_type
            if isinstance(order_type, OrderType)
            else (OrderType.MARKET if str(order_type).lower() == "market" else OrderType.LIMIT)
        )
        tif_enum = (
            time_in_force
            if isinstance(time_in_force, TimeInForce)
            else TimeInForce(str(time_in_force).upper())
        )

        req = OrderSubmitRequest(
            symbol=symbol,
            side=side_enum,
            qty=float(qty) if qty is not None else None,
            notional=float(notional) if notional is not None else None,
            order_type=type_enum,
            time_in_force=tif_enum,
            limit_price=limit_price,
            strategy_id=strategy_id,
        )
        return self.router.submit_order(req)


class _PlatformNamespace:
    def __init__(self) -> None:
        self._sb: CPZAIClient | None = None

    def configure(
        self, *, url: str | None = None, anon: str | None = None, service: str | None = None
    ) -> None:
        if url and anon:
            self._sb = CPZAIClient(url=url, api_key=anon, secret_key=service or "")
        else:
            self._sb = CPZAIClient.from_env()

    def _require(self) -> CPZAIClient:
        if self._sb is None:
            self._sb = CPZAIClient.from_env()
        return self._sb

    def health(self) -> bool:
        return self._require().health()

    def echo(self) -> dict[str, object]:
        return self._require().echo()

    def list_tables(self) -> list[str]:
        return self._require().list_tables()


class _EngineNamespace:
    """Interface to the Rust HFT Engine -- separate from broker execution.

    The engine is an execution LAYER, not a broker. Alpaca/IBKR are brokers.
    The engine sits between your strategy and the broker for microsecond-latency
    autonomous execution.

    Usage:
        client.engine.status()
        client.engine.deploy(strategy_id, symbols=["AAPL"], broker="alpaca", env="paper")
        client.engine.stop(strategy_id)
        client.engine.list_executors()
    """

    def __init__(self) -> None:
        self._stub = None
        self._channel = None
        self._rest_url: str | None = None

    def _connect(self):
        if self._stub is not None or self._rest_url is not None:
            return
        # Try gRPC first, fall back to REST
        try:
            import grpc
            from ..execution.hft_engine import engine_pb2_grpc

            engine_url = os.getenv("HFT_ENGINE_URL", "execute.cpz-lab.com:443")
            if engine_url.endswith(":443") or "cpz-lab.com" in engine_url:
                creds = grpc.ssl_channel_credentials()
                self._channel = grpc.secure_channel(engine_url, creds)
            else:
                self._channel = grpc.insecure_channel(engine_url)
            self._stub = engine_pb2_grpc.EngineServiceStub(self._channel)
        except (ImportError, Exception):
            # Fall back to REST API
            self._rest_url = os.getenv(
                "HFT_ENGINE_REST_URL",
                "http://54.90.155.250:8081"
            )

    def status(self):
        """Get engine status: name, mode, active executors, market data count."""
        self._connect()
        if self._stub:
            from ..execution.hft_engine import engine_pb2
            return self._stub.GetEngineStatus(engine_pb2.StatusRequest(), timeout=5)
        import requests as _req
        from types import SimpleNamespace
        r = _req.get(f"{self._rest_url}/health", timeout=5).json()
        return SimpleNamespace(
            engine_name=r.get("engine", "aquila-hft-engine"),
            active_executors=r.get("active_executors", 0),
            market_data_messages=0, orders_placed=0, orders_filled=0,
        )

    def deploy(
        self,
        strategy_id: str,
        symbols: list[str] | None = None,
        broker: str = "alpaca",
        environment: str = "paper",
        strategy_type: str = "momentum",
        params: dict | None = None,
        risk: dict | None = None,
        api_key: str | None = None,
        api_secret: str | None = None,
    ):
        """Deploy a strategy to the engine for autonomous execution.

        The engine creates an isolated executor that:
        1. Receives market data ticks via WebSocket (shared feed)
        2. Evaluates signals in microseconds (Rust-native)
        3. Places orders through the broker with your credentials

        Args:
            strategy_id: CPZ platform strategy ID
            symbols: Symbols to monitor (e.g., ["AAPL", "NVDA"])
            broker: Broker to use for orders ("alpaca" or "ibkr")
            environment: "paper" or "live"
            strategy_type: Signal type ("momentum", "mean_reversion", etc.)
            params: Strategy parameters
            risk: Risk limits dict
            api_key: Broker API key (auto-resolved from platform if not provided)
            api_secret: Broker API secret (auto-resolved from platform if not provided)
        """
        self._connect()
        from ..execution.hft_engine import engine_pb2
        from ..common.cpz_ai import CPZAIClient

        key = api_key or os.getenv("ALPACA_API_KEY_ID", "") or os.getenv("APCA_API_KEY_ID", "")
        secret = api_secret or os.getenv("ALPACA_API_SECRET_KEY", "") or os.getenv("APCA_API_SECRET_KEY", "")

        if not key or not secret:
            try:
                platform = CPZAIClient.from_env()
                creds = platform.get_broker_credentials(broker, env=environment)
                if creds:
                    key = creds.get("api_key_id", key)
                    secret = creds.get("api_secret_key", secret)
            except Exception:
                pass

        if not key or not secret:
            raise ValueError(
                f"Broker credentials required for engine deployment. "
                f"Set ALPACA_API_KEY_ID/ALPACA_API_SECRET_KEY or configure in CPZ platform."
            )

        user_id = os.getenv("CPZ_AI_USER_ID", "")
        if not user_id:
            try:
                platform = CPZAIClient.from_env()
                if hasattr(platform, 'user_id') and platform.user_id:
                    user_id = platform.user_id
            except Exception:
                pass
        if not user_id:
            user_id = strategy_id
        str_params = {str(k): str(v) for k, v in (params or {}).items()}
        risk_cfg = risk or {}

        if self._stub:
            from ..execution.hft_engine import engine_pb2
            req = engine_pb2.DeployStrategyRequest(
                user_id=user_id, strategy_id=strategy_id,
                credentials=engine_pb2.CredentialsConfig(
                    broker=broker, api_key=key, api_secret=secret,
                    environment=environment, user_id=user_id, strategy_id=strategy_id,
                ),
                strategy=engine_pb2.StrategyConfig(
                    strategy_type=strategy_type, params=str_params, symbols=symbols or [],
                ),
                risk=engine_pb2.RiskConfig(
                    max_position_size_usd=risk_cfg.get("max_position_size_usd", 10000),
                    max_portfolio_exposure_usd=risk_cfg.get("max_portfolio_exposure_usd", 50000),
                    max_order_rate_per_second=risk_cfg.get("max_order_rate_per_second", 10),
                    max_loss_per_day_usd=risk_cfg.get("max_loss_per_day_usd", 1000),
                ),
                symbols=symbols or [],
            )
            return self._stub.DeployStrategy(req, timeout=15)

        import requests as _req
        from types import SimpleNamespace
        payload = {
            "user_id": user_id, "strategy_id": strategy_id,
            "credentials": {
                "broker": broker, "api_key": key, "api_secret": secret,
                "environment": environment, "user_id": user_id, "strategy_id": strategy_id,
            },
            "strategy": {"strategy_type": strategy_type, "params": str_params, "symbols": symbols or []},
            "risk": risk_cfg,
            "symbols": symbols or [],
        }
        r = _req.post(f"{self._rest_url}/deploy-strategy", json=payload, timeout=15).json()
        return SimpleNamespace(success=r.get("success", False), executor_id=r.get("executor_id", ""), error=r.get("error", ""))

    def stop(self, strategy_id: str = ""):
        """Stop an executor on the engine."""
        self._connect()
        from ..common.cpz_ai import CPZAIClient
        user_id = os.getenv("CPZ_AI_USER_ID", "")
        if not user_id:
            try:
                platform = CPZAIClient.from_env()
                if hasattr(platform, 'user_id') and platform.user_id:
                    user_id = platform.user_id
            except Exception:
                pass
        if not user_id:
            user_id = strategy_id

        if self._stub:
            from ..execution.hft_engine import engine_pb2
            return self._stub.StopStrategy(
                engine_pb2.StopStrategyRequest(user_id=user_id, strategy_id=strategy_id),
                timeout=10,
            )
        import requests as _req
        from types import SimpleNamespace
        r = _req.post(f"{self._rest_url}/stop-strategy", json={"user_id": user_id, "strategy_id": strategy_id}, timeout=10).json()
        return SimpleNamespace(success=r.get("success", False), error=r.get("error", ""))

    def list_executors(self) -> list:
        """List all active executors on the engine."""
        self._connect()
        if self._stub:
            from ..execution.hft_engine import engine_pb2
            resp = self._stub.ListExecutors(engine_pb2.ListExecutorsRequest(), timeout=5)
            return list(resp.executors)
        import requests as _req
        from types import SimpleNamespace
        r = _req.get(f"{self._rest_url}/executors", timeout=5).json()
        return [SimpleNamespace(**ex) for ex in r]

    def get_executor(self, user_id: str = ""):
        """Get status of a specific executor."""
        self._connect()
        uid = user_id or os.getenv("CPZ_AI_USER_ID", "")
        if self._stub:
            from ..execution.hft_engine import engine_pb2
            try:
                return self._stub.GetExecutorStatus(
                    engine_pb2.GetExecutorStatusRequest(user_id=uid), timeout=5
                )
            except Exception:
                return None
        executors = self.list_executors()
        return next((e for e in executors if e.user_id == uid), None)


class _RiskNamespace:
    """Portfolio risk analytics — wraps cpz.risk.RiskEngine.

    Provides compute, monte_carlo, stress_test methods on the CPZClient.
    Automatically fetches risk-free rate from FRED via DataClient on first use.
    """

    def __init__(self, parent: "CPZClient") -> None:
        self._parent = parent
        self._engine: Optional[Any] = None

    def _get_engine(self) -> Any:
        if self._engine is None:
            from ..risk.engine import RiskAnalytics

            self._engine = RiskAnalytics()
            self._try_load_risk_free_rate()
        return self._engine

    def _try_load_risk_free_rate(self) -> None:
        """Best-effort fetch of 3-Month Treasury rate from FRED."""
        try:
            observations = self._parent.data.fred.get_series(
                "DGS3MO", sort_order="desc", limit=5
            )
            for obs in observations:
                val = obs.value if hasattr(obs, "value") else 0
                if isinstance(val, str):
                    val = float(val)
                if val and val > 0:
                    self._engine.set_risk_free_rate(val / 100)
                    return
        except Exception:
            pass

    def set_risk_free_rate(self, annual_rate: float) -> None:
        """Manually set the annual risk-free rate (e.g. 0.043 for 4.3%)."""
        self._get_engine().set_risk_free_rate(annual_rate)

    def compute(
        self,
        daily_returns: list,
        benchmark_returns: list,
        position_weights: dict,
        strategy_returns: Optional[dict] = None,
        strategy_metadata: Optional[dict] = None,
        total_exposure: float = 0.0,
        long_exposure: float = 0.0,
        short_exposure: float = 0.0,
    ) -> Any:
        """Compute a full risk snapshot from daily return series.

        Args:
            daily_returns: Portfolio daily returns
            benchmark_returns: Benchmark (e.g. SPY) daily returns
            position_weights: {symbol: weight} map
            strategy_returns: Optional {strategy_id: [returns]} for per-strategy analysis
            strategy_metadata: Optional {strategy_id: {"name": ..., "status": ...}}
            total_exposure: Total portfolio exposure in dollars
            long_exposure: Long exposure in dollars
            short_exposure: Short exposure in dollars

        Returns:
            RiskSnapshot with all computed metrics
        """
        return self._get_engine().compute(
            daily_returns=daily_returns,
            benchmark_returns=benchmark_returns,
            position_weights=position_weights,
            strategy_returns=strategy_returns,
            strategy_metadata=strategy_metadata,
            total_exposure=total_exposure,
            long_exposure=long_exposure,
            short_exposure=short_exposure,
        )

    def monte_carlo(
        self,
        daily_returns: list,
        num_simulations: int = 10000,
        horizon_days: int = 1,
    ) -> Any:
        """Run Monte Carlo VaR simulation.

        Args:
            daily_returns: Historical daily returns
            num_simulations: Number of simulated paths (default 10,000)
            horizon_days: Forward horizon in trading days

        Returns:
            MonteCarloResult with VaR, Expected Shortfall, percentiles
        """
        return self._get_engine().monte_carlo_var(
            daily_returns=daily_returns,
            num_simulations=num_simulations,
            horizon_days=horizon_days,
        )

    def stress_test(self, position_weights: dict, scenario: Any) -> float:
        """Apply a single stress scenario to positions. Returns impact %."""
        return self._get_engine().stress_test(position_weights, scenario)

    def stress_test_all(self, position_weights: dict) -> dict:
        """Run all historical stress scenarios. Returns {name: impact%}."""
        return self._get_engine().stress_test_all(position_weights)

    # ── Individual metrics ───────────────────────────────────────────

    def sharpe(self, daily_returns: list) -> float:
        """Annualized Sharpe ratio from daily returns."""
        return self._get_engine().sharpe(daily_returns)

    def sortino(self, daily_returns: list) -> float:
        """Annualized Sortino ratio (downside deviation only)."""
        return self._get_engine().sortino(daily_returns)

    def beta(self, daily_returns: list, benchmark_returns: list) -> float:
        """Beta vs benchmark."""
        return self._get_engine().beta(daily_returns, benchmark_returns)

    def alpha(self, daily_returns: list, benchmark_returns: list) -> float:
        """Annualized Jensen's alpha vs benchmark."""
        return self._get_engine().alpha(daily_returns, benchmark_returns)

    def volatility(self, daily_returns: list) -> float:
        """Annualized volatility (%)."""
        return self._get_engine().volatility(daily_returns)

    def max_drawdown(self, daily_returns: list) -> float:
        """Maximum drawdown (%)."""
        return self._get_engine().max_drawdown(daily_returns)

    # ── VaR methods ──────────────────────────────────────────────────

    def parametric_var(self, daily_returns: list, confidence: float = 0.95) -> float:
        """Parametric (Gaussian) VaR at given confidence. Returns % loss."""
        return self._get_engine().parametric_var(daily_returns, confidence)

    def historical_var(self, daily_returns: list, confidence: float = 0.95) -> float:
        """Historical simulation VaR. Returns % loss."""
        return self._get_engine().historical_var(daily_returns, confidence)

    # ── Advanced analytics ───────────────────────────────────────────

    def sharpe_inference(self, daily_returns: list, benchmark_sharpe: float = 0.0, num_trials: int = 1) -> Any:
        """Full Sharpe inference per Lopez de Prado (2012, 2018).

        Includes: non-normality SE correction, Lo (2002) autocorrelation adjustment,
        Probabilistic Sharpe Ratio (PSR), Deflated Sharpe Ratio (DSR) for multiple
        testing, and Minimum Track Record Length.

        Args:
            daily_returns: Daily return series
            benchmark_sharpe: Sharpe to beat (default 0)
            num_trials: Number of strategies tested (for multiple-testing correction)
        """
        return self._get_engine().sharpe_inference(daily_returns, benchmark_sharpe, num_trials)

    def drawdown_analysis(self, daily_returns: list) -> Any:
        """Detailed drawdown decomposition. Returns DrawdownAnalysis."""
        return self._get_engine().drawdown_analysis(daily_returns)

    def rolling_metrics(self, daily_returns: list, benchmark_returns: Optional[list] = None, window: int = 20) -> Any:
        """Rolling-window risk metrics. Returns RollingMetrics."""
        return self._get_engine().rolling_metrics(daily_returns, benchmark_returns, window)

    def tail_risk(self, daily_returns: list) -> Any:
        """Tail risk analysis: skewness, kurtosis, Cornish-Fisher VaR. Returns TailRiskMetrics."""
        return self._get_engine().tail_risk(daily_returns)

    def position_size(self, portfolio_value: float, daily_returns: list, **kwargs: Any) -> Any:
        """Position sizing via Kelly criterion + vol targeting. Returns PositionSizeResult."""
        return self._get_engine().position_size(portfolio_value, daily_returns, **kwargs)

    def factor_exposure(self, daily_returns: list, factor_returns: dict) -> Any:
        """Factor model decomposition via OLS. Returns FactorExposure."""
        return self._get_engine().factor_exposure(daily_returns, factor_returns)

    def correlation_matrix(self, return_series: dict) -> dict:
        """Pairwise correlation matrix from multiple return series."""
        return self._get_engine().correlation_matrix(return_series)

    def correlation_risk(self, return_series: dict) -> float:
        """Average absolute off-diagonal correlation (0-100 scale)."""
        return self._get_engine().correlation_risk(return_series)


class CPZClient(BaseClient):
    """CPZ SDK — Unified interface for trading, data, risk, and platform services.
    
    Usage:
        from cpz import CPZClient
        
        client = CPZClient()
        
        # Execution (Trading via broker)
        client.execution.use_broker("alpaca", environment="paper")
        order = client.execution.order(symbol="AAPL", qty=1, side="buy", strategy_id="my-strat")
        
        # HFT Engine (autonomous microsecond execution)
        client.engine.deploy(strategy_id, symbols=["AAPL", "NVDA"], broker="alpaca")
        client.engine.status()
        client.engine.stop(strategy_id)
        
        # Market Data
        bars = client.data.bars("AAPL", timeframe="1D", limit=100)
        
        # Risk Analytics
        snapshot = client.risk.compute(daily_returns, spy_returns, weights)
        mc = client.risk.monte_carlo(daily_returns, num_simulations=10000)
    """
    
    def __init__(self, cpz_client: Optional[CPZAIClient] = None) -> None:
        super().__init__()
        self._cpz_client = cpz_client or CPZAIClient.from_env()
        if not (cpz_client and cpz_client.is_admin):
            if not self._cpz_client.api_key or not self._cpz_client.secret_key:
                raise ValueError(
                    "CPZ API credentials are missing. Set CPZ_AI_API_KEY and CPZ_AI_SECRET_KEY "
                    "environment variables or provide a cpz_client with valid credentials."
                )
        self.execution = _ExecutionNamespace(BrokerRouter.default())
        self.engine = _EngineNamespace()
        self.platform = _PlatformNamespace()
        
        # Data client (lazy-loaded)
        self._data: Optional[DataClient] = None
        # Simons AI Assistant client (lazy-loaded)
        self._simons: Optional[SimonsClient] = None
        # Risk engine (lazy-loaded)
        self._risk: Optional["_RiskNamespace"] = None

    @classmethod
    def from_keys(
        cls, api_key: str, secret_key: str, user_id: Optional[str] = None, is_admin: bool = False
    ) -> "CPZClient":
        """Create a CPZClient with explicit API credentials.
        
        This is useful when you want to pass credentials directly instead of relying
        on environment variables, which can be helpful in some execution environments.
        
        Args:
            api_key: Your CPZAI API key
            secret_key: Your CPZAI secret key
            user_id: Optional user ID (usually auto-resolved from credentials)
            is_admin: Whether this is an admin client (default: False)
        
        Returns:
            CPZClient instance configured with the provided credentials
        """
        cpz_client = CPZAIClient.from_keys(api_key=api_key, secret_key=secret_key, user_id=user_id, is_admin=is_admin)
        return cls(cpz_client=cpz_client)

    @property
    def router(self) -> BrokerRouter:
        return self.execution.router
    
    @property
    def data(self) -> DataClient:
        """Access market data, economic data, SEC filings, and social sentiment.
        
        Simple unified methods:
            client.data.bars("AAPL")          # Stock/crypto bars
            client.data.quotes(["AAPL"])      # Latest quotes
            client.data.news("AAPL")          # News articles
            client.data.economic("GDP")       # FRED economic data
            client.data.filings("AAPL")       # SEC filings
            client.data.sentiment("AAPL")     # Social sentiment
        
        Direct provider access:
            client.data.alpaca.get_options_chain("AAPL")
            client.data.fred.get_series("UNRATE")
            client.data.twelve.get_rsi("AAPL")
            client.data.edgar.get_facts("AAPL")
        """
        if self._data is None:
            self._data = DataClient(cpz_client=self._cpz_client)
        return self._data
    
    @property
    def simons(self) -> SimonsClient:
        """Access Simons AI Assistant for trading analysis and strategy development.
        
        Simons is an AI assistant specialized in quantitative trading. It can:
        - Analyze stocks, markets, and trading strategies
        - Help write and debug trading code
        - Explain complex financial concepts
        - Propose code changes (in agent mode)
        
        Requires the 'simons' scope enabled on your API key.
        AI usage is tracked against your account's spending limits.
        
        Simple chat:
            response = client.simons.chat("Analyze AAPL for momentum trading")
            print(response.content)
        
        Streaming response:
            for chunk in client.simons.stream("Write a backtest for RSI"):
                print(chunk.content, end="", flush=True)
        
        With strategy context:
            response = client.simons.chat(
                "Explain my strategy's risk profile",
                strategy_id="your-strategy-uuid",
                mode="ask"
            )
        
        Memory management:
            memory = client.simons.get_memory()
            client.simons.update_memory(preferences={"risk_profile": "moderate"})
        """
        if self._simons is None:
            self._simons = SimonsClient(self._cpz_client)
        return self._simons

    @property
    def risk(self) -> "_RiskNamespace":
        """Portfolio risk analytics powered by numpy/scipy.

        Compute:
            snapshot = client.risk.compute(daily_returns, spy_returns, weights)

        Monte Carlo VaR:
            mc = client.risk.monte_carlo(daily_returns, num_simulations=10000)

        Stress testing:
            impacts = client.risk.stress_test_all(position_weights)

        Set risk-free rate (fetched from FRED automatically when using data):
            client.risk.set_risk_free_rate(0.043)
        """
        if self._risk is None:
            self._risk = _RiskNamespace(self)
        return self._risk

    # File operations - delegate to CPZAIClient
    def upload_dataframe(
        self, bucket_name: str, file_path: str, df: Any, format: str = "csv", **kwargs
    ) -> Optional[dict[str, Any]]:
        """Upload a pandas DataFrame to storage"""
        return self._cpz_client.upload_dataframe(bucket_name, file_path, df, format=format, **kwargs)

    def download_csv_to_dataframe(
        self, bucket_name: str, file_path: str, encoding: str = "utf-8", **kwargs
    ) -> Optional[Any]:
        """Download a CSV file and load it into a pandas DataFrame"""
        return self._cpz_client.download_csv_to_dataframe(
            bucket_name, file_path, encoding=encoding, **kwargs
        )

    def download_json_to_dataframe(
        self, bucket_name: str, file_path: str, **kwargs
    ) -> Optional[Any]:
        """Download a JSON file and load it into a pandas DataFrame"""
        return self._cpz_client.download_json_to_dataframe(bucket_name, file_path, **kwargs)

    def download_parquet_to_dataframe(
        self, bucket_name: str, file_path: str, **kwargs
    ) -> Optional[Any]:
        """Download a Parquet file and load it into a pandas DataFrame"""
        return self._cpz_client.download_parquet_to_dataframe(bucket_name, file_path, **kwargs)

    def list_files_in_bucket(
        self, bucket_name: str, prefix: str = "", limit: int = 100
    ) -> list[dict[str, Any]]:
        """List files in a storage bucket with optional prefix filtering"""
        return self._cpz_client.list_files_in_bucket(bucket_name, prefix=prefix, limit=limit)

    def delete_file(self, bucket_name: str, file_path: str) -> bool:
        """Delete a file from storage"""
        return self._cpz_client.delete_file(bucket_name, file_path)

    # Strategy operations - delegate to CPZAIClient
    def list_strategies(self, limit: int = 100, offset: int = 0) -> list[dict[str, Any]]:
        """List your trading strategies."""
        return self._cpz_client.get_strategies(limit=limit, offset=offset)

    def get_strategy(self, strategy_id: str) -> Optional[dict[str, Any]]:
        """Get a specific strategy by ID."""
        return self._cpz_client.get_strategy(strategy_id)

    def create_strategy(self, strategy_data: dict[str, Any]) -> Optional[dict[str, Any]]:
        """Create a new strategy."""
        return self._cpz_client.create_strategy(strategy_data)

    def update_strategy(self, strategy_id: str, strategy_data: dict[str, Any]) -> Optional[dict[str, Any]]:
        """Update an existing strategy."""
        return self._cpz_client.update_strategy(strategy_id, strategy_data)

    def delete_strategy(self, strategy_id: str) -> bool:
        """Delete a strategy."""
        return self._cpz_client.delete_strategy(strategy_id)

    @staticmethod
    def display_with_sticky_header(
        df: Any, max_rows: int = 1000, max_height: str = "600px", show_index: bool = True
    ) -> None:
        """
        Display a pandas DataFrame with a sticky/fixed header that stays visible while scrolling.
        
        This is useful for viewing long tables where you want the header row to remain visible
        while scrolling through the data.
        
        Args:
            df: The pandas DataFrame to display
            max_rows: Maximum number of rows to display (default: 1000)
            max_height: Maximum height of the table container before scrolling (default: "600px")
            show_index: Whether to show the DataFrame index column (default: True)
        
        Example:
            >>> df = client.download_csv_to_dataframe("user-data", "file.csv")
            >>> client.display_with_sticky_header(df)
        """
        try:
            import pandas as pd
            from ..common.display import display_dataframe_with_sticky_header
            if isinstance(df, pd.DataFrame):
                display_dataframe_with_sticky_header(df, max_rows=max_rows, max_height=max_height, show_index=show_index)
            else:
                print("Error: df must be a pandas DataFrame")
        except ImportError:
            print("Error: pandas is required for display_with_sticky_header")
