"""
Rithmic Bridge クライアントライブラリ

外部のPythonスクリプトから容易に利用できる高レベルAPIを提供する。
ZeroMQの通信詳細を隠蔽し、イベント駆動型のインターフェースを実現する。
"""

import json
import logging
import threading
import time
from typing import Callable, List, Optional, Union

import zmq

from ..config import BridgeConfig, get_config
from ..models import MBOEvent, TradeEvent, DataType
from .receiver import ZmqReceiver

logger = logging.getLogger(__name__)


class RithmicClient:
    """
    Rithmic Bridge クライアント
    
    サーバー（Publisher）に接続し、データ受信とコマンド送信を行う。
    """

    def __init__(self, host: str = "localhost", port: int = 5555, req_port: int = 5556):
        """
        Args:
            host: 接続先ホスト
            port: データ受信ポート (SUB)
            req_port: コマンド送信ポート (REQ)
        """
        self.config = get_config()
        self.config.zmq.client_host = host
        self.config.zmq.client_port = port
        self.config.zmq.req_port = req_port
        
        self.receiver = ZmqReceiver(self.config)
        self._req_socket: Optional[zmq.Socket] = None
        self._req_context: Optional[zmq.Context] = None
        self._req_lock = threading.Lock()
        
        # コールバック
        self._mbo_callbacks: List[Callable[[MBOEvent], None]] = []
        self._trade_callbacks: List[Callable[[TradeEvent], None]] = []
        
        # レシーバーのコールバックを登録
        self.receiver.on_mbo(self._handle_mbo)
        self.receiver.on_trade(self._handle_trade)

    def connect(self) -> None:
        """サーバーに接続する"""
        logger.info(f"Rithmic Bridgeに接続中... {self.config.get_sub_connect_addr()}")
        
        # データ受信開始
        self.receiver.start()
        
        # コマンド送信ソケットの準備
        self._req_context = zmq.Context()
        self._req_socket = self._req_context.socket(zmq.REQ)
        req_addr = f"tcp://{self.config.zmq.client_host}:{self.config.zmq.req_port}"
        self._req_socket.connect(req_addr)
        logger.info(f"コマンドチャネル接続: {req_addr}")

    def disconnect(self) -> None:
        """切断する"""
        self.receiver.stop()
        
        if self._req_socket:
            self._req_socket.close()
            self._req_socket = None
        if self._req_context:
            self._req_context.term()
            self._req_context = None
            
        logger.info("切断しました")

    def subscribe(self, symbol: str, exchange: str) -> bool:
        """
        銘柄を購読する
        
        Args:
            symbol: シンボル名 (例: GCJ6)
            exchange: 取引所 (例: COMEX)
            
        Returns:
            bool: 成功した場合True
        """
        cmd = {
            "command": "subscribe",
            "symbol": symbol,
            "exchange": exchange
        }
        resp = self._send_command(cmd)
        if resp and resp.get("status") == "ok":
            logger.info(f"購読成功: {symbol}.{exchange}")
            return True
        else:
            logger.error(f"購読失敗: {resp}")
            return False

    def list_subscriptions(self) -> List[str]:
        """現在の購読リストを取得する"""
        cmd = {"command": "list_subscriptions"}
        resp = self._send_command(cmd)
        if resp and resp.get("status") == "ok":
            return resp.get("subscriptions", [])
        return []

    def on_mbo(self, callback: Callable[[MBOEvent], None]) -> None:
        """MBOイベント受信時のコールバックを登録"""
        self._mbo_callbacks.append(callback)

    def on_trade(self, callback: Callable[[TradeEvent], None]) -> None:
        """Tradeイベント受信時のコールバックを登録"""
        self._trade_callbacks.append(callback)

    def _handle_mbo(self, event: MBOEvent) -> None:
        """MBOイベントの内部ハンドラ"""
        for cb in self._mbo_callbacks:
            try:
                cb(event)
            except Exception as e:
                logger.error(f"MBOコールバックエラー: {e}")

    def _handle_trade(self, event: TradeEvent) -> None:
        """Tradeイベントの内部ハンドラ"""
        for cb in self._trade_callbacks:
            try:
                cb(event)
            except Exception as e:
                logger.error(f"Tradeコールバックエラー: {e}")

    def _send_command(self, cmd: dict) -> Optional[dict]:
        """コマンドを送信してレスポンスを受け取る"""
        if not self._req_socket:
            logger.warning("コマンドチャネルが未接続です")
            return None
            
        with self._req_lock:
            try:
                self._req_socket.send_json(cmd)
                
                # タイムアウト付きで受信（簡易実装: ポーリング）
                # サーバー側でR|API+の安定化待機(2秒)があるため、余裕を見て10秒に設定
                if self._req_socket.poll(timeout=10000):
                    return self._req_socket.recv_json()
                else:
                    logger.error("コマンド応答タイムアウト")
                    # タイムアウト時はソケットを再作成する必要がある（REQ/REPパターンのお約束）
                    self._reconnect_req()
                    return None
            except Exception as e:
                logger.error(f"コマンド送信エラー: {e}")
                return None

    def _reconnect_req(self) -> None:
        """REQソケットの再接続"""
        if self._req_socket:
            self._req_socket.close(linger=0)
        self._req_socket = self._req_context.socket(zmq.REQ)
        req_addr = f"tcp://{self.config.zmq.client_host}:{self.config.zmq.req_port}"
        self._req_socket.connect(req_addr)
