"""
ZeroMQ PUBサーバーモジュール

MBO/Tradeイベントをスレッドセーフなキュー経由で受け取り、
ZeroMQ PUBソケットからパブリッシュする。

R|API+のコールバックは別スレッドで非同期に発火するため、
送信処理へのブリッジにqueue.Queueを使用してスレッドセーフ性を確保する。
"""

import json
import logging
import queue
import threading
import time
from typing import Optional

import zmq
from rich.console import Console
from rich.logging import RichHandler

from ..config import BridgeConfig
from ..models import TOPIC_MBO, TOPIC_TRADE, TOPIC_HEARTBEAT, DataType, HeartbeatEvent

# ロガー設定
logger = logging.getLogger(__name__)
console = Console()


class ZmqPublisher:
    """
    ZeroMQ PUBソケットを管理するパブリッシャー。

    内部キューからメッセージを取得し、トピック付きで
    ZeroMQ PUBソケットにパブリッシュする。
    """

    def __init__(self, config: BridgeConfig):
        """
        Args:
            config: ブリッジ設定オブジェクト
        """
        self.config = config
        # スレッドセーフなメッセージキュー
        self._queue: queue.Queue = queue.Queue(maxsize=config.zmq.hwm)
        # ZeroMQコンテキストとソケット
        self._context: Optional[zmq.Context] = None
        self._socket: Optional[zmq.Socket] = None
        # パブリッシャースレッド
        self._thread: Optional[threading.Thread] = None
        # 停止フラグ
        self._running = threading.Event()
        # 統計情報
        self._stats = {
            "mbo_count": 0,
            "trade_count": 0,
            "errors": 0,
            "start_time": 0.0,
        }
        # Heartbeat 設定
        self._heartbeat_interval = 1.0  # 1秒間隔
        self._heartbeat_thread: Optional[threading.Thread] = None
        self._heartbeat_seq = 0
        # Heartbeat用停止イベント（clearでsleep、setで即時停止）
        self._heartbeat_stop = threading.Event()

    def start(self, enable_heartbeat: bool = True) -> None:
        """
        パブリッシャーを開始する

        Args:
            enable_heartbeat: Heartbeat自動送信を有効にするか (Default: True)
        """
        if self._running.is_set():
            logger.warning("パブリッシャーは既に実行中です")
            return

        bind_addr = self.config.get_pub_bind_addr()
        logger.info(f"ZeroMQ PUBソケットを {bind_addr} でバインド開始...")

        # ZeroMQコンテキストとソケットの初期化
        self._context = zmq.Context()
        self._socket = self._context.socket(zmq.PUB)
        # 高水位マーク（HWM）の設定 - バッファオーバーフロー防止
        self._socket.setsockopt(zmq.SNDHWM, self.config.zmq.hwm)
        # ソケットのlinger設定 - クローズ時に未送信メッセージを待つ時間(ms)
        self._socket.setsockopt(zmq.LINGER, 1000)
        self._socket.bind(bind_addr)

        # パブリッシャースレッド開始
        self._running.set()
        self._stats["start_time"] = time.time()
        self._thread = threading.Thread(
            target=self._publish_loop,
            name="zmq-publisher",
            daemon=True,
        )
        self._thread.start()

        console.print(
            f"[bold green]✓ ZeroMQ PUBサーバー開始[/] - {bind_addr}",
        )

        # Heartbeat送信スレッド開始（オプション）
        if enable_heartbeat:
            self._heartbeat_thread = threading.Thread(
                target=self._heartbeat_loop,
                name="zmq-heartbeat",
                daemon=True,
            )
            self._heartbeat_thread.start()
            logger.info(f"Heartbeat送信開始 - 間隔: {self._heartbeat_interval}秒")

    def stop(self) -> None:
        """パブリッシャーを停止する"""
        if not self._running.is_set():
            return

        logger.info("パブリッシャーを停止中...")
        self._running.clear()
        # Heartbeatスレッドを即時停止
        self._heartbeat_stop.set()

        # スレッド終了を待機
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=5.0)
        if self._heartbeat_thread and self._heartbeat_thread.is_alive():
            self._heartbeat_thread.join(timeout=2.0)

        # ソケットとコンテキストのクリーンアップ
        if self._socket:
            self._socket.close()
            self._socket = None
        if self._context:
            self._context.term()
            self._context = None

        # 統計情報の表示
        self._print_stats()
        console.print("[bold yellow]■ ZeroMQ PUBサーバー停止[/]")

    def enqueue(self, topic: bytes, message: str) -> bool:
        """
        メッセージをパブリッシュキューに追加する。

        R|API+のコールバックスレッドから安全に呼び出せる。

        Args:
            topic: ZeroMQトピック (TOPIC_MBO または TOPIC_TRADE)
            message: JSON文字列のメッセージ

        Returns:
            bool: キューに追加できた場合True
        """
        try:
            self._queue.put_nowait((topic, message))
            return True
        except queue.Full:
            logger.warning("パブリッシュキューが満杯 - メッセージをドロップ")
            self._stats["errors"] += 1
            return False

    def enqueue_mbo(self, message: str) -> bool:
        """MBOイベントをキューに追加"""
        return self.enqueue(TOPIC_MBO, message)

    def enqueue_trade(self, message: str) -> bool:
        """Tradeイベントをキューに追加"""
        return self.enqueue(TOPIC_TRADE, message)

    def enqueue_heartbeat(self, message: str) -> bool:
        """Heartbeatイベントをキューに追加"""
        return self.enqueue(TOPIC_HEARTBEAT, message)

    def _publish_loop(self) -> None:
        """
        パブリッシュループ（別スレッドで実行）。

        キューからメッセージを取得し、ZeroMQ PUBソケットに送信する。
        マルチパートメッセージ（トピック + データ）を使用。
        """
        logger.info("パブリッシュループ開始")

        while self._running.is_set():
            try:
                # キューからメッセージ取得（タイムアウト付き）
                topic, message = self._queue.get(timeout=0.1)

                # マルチパートメッセージとして送信
                # [トピック, JSON文字列]
                self._socket.send_multipart([
                    topic,
                    message.encode("utf-8"),
                ])

                # 統計更新
                if topic == TOPIC_MBO:
                    self._stats["mbo_count"] += 1
                elif topic == TOPIC_TRADE:
                    self._stats["trade_count"] += 1

            except queue.Empty:
                # タイムアウト - ループ継続
                continue
            except zmq.ZMQError as e:
                logger.error(f"ZeroMQ送信エラー: {e}")
                self._stats["errors"] += 1
            except Exception as e:
                logger.error(f"パブリッシュエラー: {e}")
                self._stats["errors"] += 1

        logger.info("パブリッシュループ終了")

    def _heartbeat_loop(self) -> None:
        """
        Heartbeat送信ループ（別スレッドで実行）。

        定期的にHeartbeatイベントを送信して生存確認を行う。
        シーケンス番号とパブリッシャー側の統計情報を含める。
        """
        logger.info("Heartbeat送信ループ開始")

        while self._running.is_set():
            try:
                # Heartbeatイベント生成
                self._heartbeat_seq += 1
                hb = HeartbeatEvent(
                    seq=self._heartbeat_seq,
                    pub_mbo_count=self._stats["mbo_count"],
                    pub_trade_count=self._stats["trade_count"],
                )
                self.enqueue_heartbeat(hb.to_json())

            except Exception as e:
                logger.error(f"Heartbeat送信エラー: {e}")

            # 次のHeartbeatまで待機（停止時は即時返却）
            # _heartbeat_stopはset()で即座に返る、通常時はタイムアウトまでブロック
            self._heartbeat_stop.wait(timeout=self._heartbeat_interval)

    def _print_stats(self) -> None:
        """統計情報をリッチコンソールで表示"""
        elapsed = time.time() - self._stats["start_time"]
        total = self._stats["mbo_count"] + self._stats["trade_count"]
        rate = total / elapsed if elapsed > 0 else 0

        from rich.table import Table

        table = Table(title="パブリッシャー統計")
        table.add_column("項目", style="cyan")
        table.add_column("値", style="green")
        table.add_row("MBOイベント数", str(self._stats["mbo_count"]))
        table.add_row("Tradeイベント数", str(self._stats["trade_count"]))
        table.add_row("合計", str(total))
        table.add_row("エラー数", str(self._stats["errors"]))
        table.add_row("稼働時間", f"{elapsed:.1f}秒")
        table.add_row("レート", f"{rate:.1f} msg/s")
        console.print(table)

    @property
    def is_running(self) -> bool:
        """パブリッシャーが実行中かどうか"""
        return self._running.is_set()

    @property
    def queue_size(self) -> int:
        """現在のキューサイズ"""
        return self._queue.qsize()


class ZmqCommandServer:
    """
    ZeroMQ REPソケットでコマンドを受け付けるサーバー。
    
    クライアントからの購読リクエストなどを処理し、
    RapiBridgeを操作する。
    """

    def __init__(self, config: BridgeConfig, bridge_ref=None):
        """
        Args:
            config: ブリッジ設定
            bridge_ref: RapiBridgeインスタンスへの参照 (循環参照回避のため型ヒントなし)
        """
        self.config = config
        self.bridge = bridge_ref
        self._context: Optional[zmq.Context] = None
        self._socket: Optional[zmq.Socket] = None
        self._thread: Optional[threading.Thread] = None
        self._running = threading.Event()

    def start(self) -> None:
        """コマンドサーバーを開始"""
        if self._running.is_set():
            return

        bind_addr = f"tcp://*:{self.config.zmq.rep_port}"
        logger.info(f"ZeroMQ REPソケットを {bind_addr} でバインド開始...")

        self._context = zmq.Context()
        self._socket = self._context.socket(zmq.REP)
        self._socket.bind(bind_addr)

        self._running.set()
        self._thread = threading.Thread(
            target=self._command_loop,
            name="zmq-command-server",
            daemon=True,
        )
        self._thread.start()
        logger.info(f"✓ ZeroMQ REPサーバー開始 - {bind_addr}")

    def stop(self) -> None:
        """コマンドサーバーを停止"""
        if not self._running.is_set():
            return
            
        logger.info("コマンドサーバーを停止中...")
        self._running.clear()
        
        # ソケットを閉じてスレッドを終了させる（REPはブロッキングなので工夫が必要だが今回は簡易停止）
        if self._context:
            self._context.term() # Contextを強制終了してrecvを解除
            
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=2.0)
            
        logger.info("■ ZeroMQ REPサーバー停止")

    def _command_loop(self) -> None:
        """コマンド受信ループ"""
        while self._running.is_set():
            try:
                # リクエスト受信
                message = self._socket.recv_json()
                logger.info(f"コマンド受信: {message}")
                
                # 処理実行
                response = self._process_command(message)
                
                # レスポンス返信
                self._socket.send_json(response)
                
            except zmq.ZMQError as e:
                if self._running.is_set():
                    logger.error(f"ZeroMQ REPエラー: {e}")
            except Exception as e:
                logger.error(f"コマンド処理エラー: {e}")
                try:
                    self._socket.send_json({"status": "error", "message": str(e)})
                except: pass

    def _process_command(self, cmd: dict) -> dict:
        """コマンドに応じた処理を実行"""
        command = cmd.get("command")
        
        if command == "subscribe":
            symbol = cmd.get("symbol")
            exchange = cmd.get("exchange")
            if not symbol or not exchange:
                return {"status": "error", "message": "symbol and exchange are required"}
            
            if self.bridge:
                # Bridgeの購読メソッドを呼び出す (スレッドセーフ性はBridge側で担保)
                # TODO: Bridge側に add_subscription メソッドを追加する
                success = self.bridge.add_subscription(symbol, exchange)
                if success:
                    return {"status": "ok", "message": f"Subscribed to {symbol}.{exchange}"}
                else:
                    return {"status": "error", "message": "Subscription failed"}
            else:
                return {"status": "error", "message": "Bridge not initialized"}

        elif command == "list_subscriptions":
            if self.bridge:
                subs = self.bridge.get_subscriptions()
                return {"status": "ok", "subscriptions": subs}
            else:
                return {"status": "error", "message": "Bridge not initialized"}
        
        elif command == "disconnect":
            if self.bridge:
                self.bridge.disconnect()
                return {"status": "ok", "message": "Disconnected from Rithmic"}
            
        return {"status": "error", "message": f"Unknown command: {command}"}
