"""
HFT Engine broker adapter.

Routes orders through the Aquila HFT Engine (Rust) via gRPC instead of
directly to a broker's REST API. The engine handles order routing with
sub-millisecond latency.

Credential flow:
  1. Credentials come from the same sources as other adapters:
     kwargs -> env vars -> CPZ platform (trading_credentials)
  2. On adapter creation, credentials are forwarded to the engine
     via the ConfigureCredentials gRPC call
  3. The engine uses those credentials to connect to the broker
"""

from __future__ import annotations

import os
import logging
from datetime import datetime
from typing import AsyncIterator, Iterable, Optional

import grpc

from ..enums import OrderSide, OrderType, TimeInForce
from ..interfaces import BrokerAdapter
from ..models import (
    Account,
    Order,
    OrderReplaceRequest,
    OrderSubmitRequest,
    Position,
    Quote,
    Bar,
)
from ...common.cpz_ai import CPZAIClient

from . import engine_pb2
from . import engine_pb2_grpc

logger = logging.getLogger(__name__)

DEFAULT_ENGINE_URL = "execute.cpz-lab.com:443"


class HFTEngineAdapter(BrokerAdapter):
    """Broker adapter that routes through the Aquila HFT Engine via gRPC."""

    def __init__(
        self,
        engine_url: str,
        broker: str,
        api_key: str,
        api_secret: str,
        env: str = "paper",
        account_id: str = "",
        user_id: str = "",
        strategy_id: str = "",
    ) -> None:
        self._engine_url = engine_url
        self._broker = broker
        self._env = env
        self._account_id = account_id
        self._api_key = api_key
        self._api_secret = api_secret
        self._user_id = user_id
        self._strategy_id = strategy_id

        if engine_url.endswith(":443") or engine_url.startswith("execute.cpz-lab.com"):
            credentials = grpc.ssl_channel_credentials()
            self._channel = grpc.secure_channel(engine_url, credentials)
        else:
            self._channel = grpc.insecure_channel(engine_url)
        self._stub = engine_pb2_grpc.EngineServiceStub(self._channel)

        creds_req = engine_pb2.CredentialsConfig(
            broker=broker,
            api_key=api_key,
            api_secret=api_secret,
            environment=env,
            account_id=account_id,
            user_id=user_id,
            strategy_id=strategy_id,
        )
        resp = self._stub.ConfigureCredentials(creds_req, timeout=10)
        if not resp.success:
            raise ConnectionError(
                f"HFT Engine rejected credentials: {resp.error}"
            )
        logger.info(
            "HFT Engine configured: broker=%s env=%s engine=%s",
            broker, env, engine_url,
        )

    @staticmethod
    def create(**kwargs) -> "HFTEngineAdapter":
        engine_url = str(
            kwargs.get("engine_url")
            or os.getenv("HFT_ENGINE_URL", DEFAULT_ENGINE_URL)
        )
        broker = str(kwargs.get("broker") or os.getenv("HFT_BROKER", "alpaca"))
        env = str(kwargs.get("env") or os.getenv("ALPACA_ENV", "paper"))
        account_id = str(kwargs.get("account_id") or "")
        user_id = str(
            kwargs.get("user_id")
            or os.getenv("CPZ_AI_USER_ID", "")
        )
        strategy_id = str(
            kwargs.get("strategy_id")
            or os.getenv("CPZ_AI_STRATEGY_ID", "")
            or os.getenv("CPZ_STRATEGY_ID", "")
        )

        api_key = str(kwargs.get("api_key_id") or kwargs.get("api_key") or "")
        api_secret = str(kwargs.get("api_secret_key") or kwargs.get("api_secret") or "")

        if not api_key:
            api_key = os.getenv("ALPACA_API_KEY_ID", "") or os.getenv("APCA_API_KEY_ID", "")
        if not api_secret:
            api_secret = os.getenv("ALPACA_API_SECRET_KEY", "") or os.getenv("APCA_API_SECRET_KEY", "")

        if not api_key or not api_secret:
            platform = CPZAIClient.from_env()
            lookup_env = None if account_id else (env or None)
            try:
                creds = platform.get_broker_credentials(
                    broker, env=lookup_env, account_id=(account_id or None)
                )
                if creds and creds.get("api_key_id") and creds.get("api_secret_key"):
                    api_key = creds.get("api_key_id", api_key)
                    api_secret = creds.get("api_secret_key", api_secret)
                    if creds.get("env"):
                        env = str(creds["env"])
                    if not account_id and creds.get("account_id"):
                        account_id = str(creds["account_id"])
                elif creds is None:
                    raise ValueError(
                        f"Broker credentials not found in CPZ platform for "
                        f"broker={broker}, env={env}, account_id={account_id}. "
                        f"Add credentials at https://ai.cpz-lab.com/execution"
                    )
            except ValueError:
                raise
            except Exception as exc:
                logger.warning("Failed to fetch broker credentials from CPZ: %s", exc)
                if not api_key or not api_secret:
                    raise ValueError(
                        f"Broker credentials required but unavailable. "
                        f"Set ALPACA_API_KEY_ID/ALPACA_API_SECRET_KEY env vars, "
                        f"or configure in CPZ platform. Error: {exc}"
                    ) from exc

        if not api_key or not api_secret:
            raise ValueError(
                "Broker API credentials required for HFT Engine. "
                "Set ALPACA_API_KEY_ID and ALPACA_API_SECRET_KEY, "
                "or configure in CPZ platform."
            )

        return HFTEngineAdapter(
            engine_url=engine_url,
            broker=broker,
            api_key=api_key,
            api_secret=api_secret,
            env=env,
            account_id=account_id,
            user_id=user_id,
            strategy_id=strategy_id,
        )

    def get_account(self) -> Account:
        resp = self._stub.GetAccount(engine_pb2.AccountRequest(), timeout=5)
        return Account(
            id=resp.account_id,
            buying_power=float(resp.buying_power or 0),
            equity=float(resp.equity or 0),
            cash=float(resp.cash or 0),
        )

    def get_positions(self) -> list[Position]:
        resp = self._stub.GetPositions(engine_pb2.PositionsRequest(), timeout=5)
        return [
            Position(
                symbol=p.symbol,
                qty=float(p.quantity),
                avg_entry_price=float(p.avg_entry_price),
                market_value=float(p.market_value),
            )
            for p in resp.positions
        ]

    def submit_order(self, req: OrderSubmitRequest) -> Order:
        grpc_req = engine_pb2.OrderRequest(
            symbol=req.symbol,
            side=req.side.value,
            order_type=req.type.value,
            quantity=str(req.qty),
            limit_price=str(req.limit_price) if req.limit_price else "",
            stop_price="",
            time_in_force=req.time_in_force.value.lower(),
            strategy_id=getattr(req, "strategy_id", "") or "",
            client_order_id="",
        )
        resp = self._stub.PlaceOrder(grpc_req, timeout=10)

        if resp.error:
            raise RuntimeError(f"HFT Engine order rejected: {resp.error}")

        return Order(
            id=resp.order_id or resp.client_order_id,
            symbol=req.symbol,
            side=req.side,
            qty=req.qty,
            type=req.type,
            time_in_force=req.time_in_force,
            status=resp.status or "pending_new",
            filled_qty=float(resp.filled_qty) if resp.filled_qty else None,
            average_fill_price=float(resp.filled_avg_price) if resp.filled_avg_price else None,
        )

    def get_order(self, order_id: str) -> Order:
        resp = self._stub.GetOrder(
            engine_pb2.GetOrderRequest(order_id=order_id), timeout=5
        )
        return Order(
            id=resp.order_id,
            symbol="",
            side=OrderSide.BUY,
            qty=0,
            type=OrderType.MARKET,
            time_in_force=TimeInForce.DAY,
            status=resp.status or "unknown",
            filled_qty=float(resp.filled_qty) if resp.filled_qty else None,
            average_fill_price=float(resp.filled_avg_price) if resp.filled_avg_price else None,
        )

    def cancel_order(self, order_id: str) -> Order:
        resp = self._stub.CancelOrder(
            engine_pb2.CancelRequest(order_id=order_id), timeout=5
        )
        if not resp.success:
            raise RuntimeError(f"Cancel failed: {resp.error}")
        return self.get_order(order_id)

    def replace_order(self, order_id: str, req: OrderReplaceRequest) -> Order:
        raise NotImplementedError(
            "Order replacement not yet supported by HFT Engine. "
            "Cancel and resubmit instead."
        )

    def stream_quotes(self, symbols: Iterable[str]) -> AsyncIterator[Quote]:
        async def _stream():
            req = engine_pb2.SubscribeRequest(
                symbols=list(symbols),
                data_types=["quotes"],
            )
            for update in self._stub.StreamMarketData(req):
                yield Quote(
                    symbol=update.symbol,
                    bid=update.bid,
                    ask=update.ask,
                    bid_size=update.bid_size,
                    ask_size=update.ask_size,
                )
        return _stream()

    def get_quotes(self, symbols: list[str]) -> list[Quote]:
        raise NotImplementedError(
            "Use stream_quotes() for real-time data from HFT Engine"
        )

    def get_historical_data(
        self,
        symbol: str,
        timeframe: str,
        limit: int = 100,
        start: Optional[datetime] = None,
        end: Optional[datetime] = None,
    ) -> list[Bar]:
        raise NotImplementedError(
            "Historical data not available via HFT Engine. "
            "Use client.data provider instead."
        )

    # --- Multi-tenant executor management ---

    def deploy_strategy(
        self,
        strategy_id: str,
        strategy_type: str = "custom",
        params: Optional[dict] = None,
        symbols: Optional[list] = None,
        risk: Optional[dict] = None,
    ):
        """Deploy a strategy to the engine as an isolated executor.

        Args:
            strategy_id: CPZ platform strategy ID
            strategy_type: Signal type (e.g., "momentum", "mean_reversion")
            params: Strategy parameters (key-value pairs)
            symbols: Symbols to subscribe to (e.g., ["SPY", "QQQ"])
            risk: Risk configuration dict with keys: max_position_size_usd,
                  max_portfolio_exposure_usd, max_order_rate_per_second,
                  max_loss_per_day_usd

        Returns:
            DeployStrategyResponse with success, executor_id, error
        """
        risk_config = engine_pb2.RiskConfig(
            max_position_size_usd=(risk or {}).get("max_position_size_usd", 10000),
            max_portfolio_exposure_usd=(risk or {}).get("max_portfolio_exposure_usd", 50000),
            max_order_rate_per_second=(risk or {}).get("max_order_rate_per_second", 10),
            max_loss_per_day_usd=(risk or {}).get("max_loss_per_day_usd", 1000),
        )

        str_params = {str(k): str(v) for k, v in (params or {}).items()}

        req = engine_pb2.DeployStrategyRequest(
            user_id=self._user_id,
            strategy_id=strategy_id,
            credentials=engine_pb2.CredentialsConfig(
                broker=self._broker,
                api_key=self._api_key,
                api_secret=self._api_secret,
                environment=self._env,
                account_id=self._account_id,
                user_id=self._user_id,
                strategy_id=strategy_id,
            ),
            strategy=engine_pb2.StrategyConfig(
                strategy_type=strategy_type,
                params=str_params,
                symbols=symbols or [],
            ),
            risk=risk_config,
            symbols=symbols or [],
        )
        resp = self._stub.DeployStrategy(req, timeout=15)
        logger.info(
            "DeployStrategy: success=%s executor_id=%s",
            resp.success, resp.executor_id,
        )
        return resp

    def stop_strategy(self, strategy_id: str = ""):
        """Stop a strategy executor on the engine.

        Args:
            strategy_id: Strategy ID to stop (optional, stops the user's executor)

        Returns:
            ConfigResponse with success, error
        """
        req = engine_pb2.StopStrategyRequest(
            user_id=self._user_id,
            strategy_id=strategy_id or self._strategy_id,
        )
        resp = self._stub.StopStrategy(req, timeout=10)
        logger.info("StopStrategy: success=%s", resp.success)
        return resp

    def list_executors(self) -> list:
        """List all active executors on the engine.

        Returns:
            List of ExecutorInfo objects with user_id, strategy_id, status, symbols
        """
        resp = self._stub.ListExecutors(
            engine_pb2.ListExecutorsRequest(), timeout=5
        )
        return list(resp.executors)

    def get_executor_status(self, user_id: str = "") -> Optional[object]:
        """Get status of a specific executor.

        Args:
            user_id: User ID to check (defaults to current user)

        Returns:
            ExecutorInfo or None
        """
        try:
            resp = self._stub.GetExecutorStatus(
                engine_pb2.GetExecutorStatusRequest(
                    user_id=user_id or self._user_id
                ),
                timeout=5,
            )
            return resp
        except grpc.RpcError as e:
            if e.code() == grpc.StatusCode.NOT_FOUND:
                return None
            raise

    def get_engine_status(self):
        """Get overall engine status including executor count.

        Returns:
            StatusResponse with engine info and active_executors count
        """
        return self._stub.GetEngineStatus(
            engine_pb2.StatusRequest(), timeout=5
        )
