"""
ZeroMQ PUB/SUB通信テスト

ローカルでのPUB→SUB通信をテストする。
実際にPUB/SUBソケットを立ち上げて
メッセージの送受信を検証する。
"""

import json
import time
import threading

import zmq
import pytest

from rithmic_bridge.config import BridgeConfig, ZmqConfig
from rithmic_bridge.models import (
    MBOEvent, TradeEvent,
    MBOAction, Side, TradeAction,
    TOPIC_MBO, TOPIC_TRADE,
)
from rithmic_bridge.publisher.server import ZmqPublisher
from rithmic_bridge.client.receiver import ZmqReceiver


def make_test_config(port: int) -> BridgeConfig:
    """テスト用の設定を生成する（ポート衝突を避けるため）"""
    return BridgeConfig(
        zmq=ZmqConfig(
            pub_addr=f"tcp://127.0.0.1:{port}",
            pub_port=port,
            client_host="127.0.0.1",
            client_port=port,
            hwm=100,  # テスト用: heartbeat送信によるキュー溢れを防止
        )
    )


class TestZmqPubSub:
    """ZeroMQ PUB/SUB通信テスト"""

    def test_mbo_event_roundtrip(self):
        """MBOイベントのPUB→SUBラウンドトリップが正しいこと"""
        port = 15555
        config = make_test_config(port)

        # パブリッシャー起動
        publisher = ZmqPublisher(config)
        publisher.start(enable_heartbeat=False)

        # 少し待ってからレシーバー起動（PUBソケットのバインド待ち）
        time.sleep(0.2)

        # 受信イベント用リスト
        received_events = []

        receiver = ZmqReceiver(config)
        receiver.on_mbo(lambda e: received_events.append(e))
        receiver.start(topics=[TOPIC_MBO])

        # SUBソケットの接続確立待ち
        time.sleep(0.5)

        # テストイベント送信
        event = MBOEvent(
            symbol="GCJ6.COMEX",
            action=MBOAction.ADD,
            order_id="test123",
            side=Side.BID,
            price=2500.0,
            size=5,
            timestamp=1708170000.0,
        )
        publisher.enqueue_mbo(event.to_json())

        # 受信待ち
        time.sleep(1.0)

        # クリーンアップ
        receiver.stop()
        publisher.stop()

        # 検証
        assert len(received_events) >= 1
        rx_event = received_events[0]
        assert isinstance(rx_event, MBOEvent)
        assert rx_event.symbol == "GCJ6.COMEX"
        assert rx_event.order_id == "test123"
        assert rx_event.side == Side.BID
        assert rx_event.price == 2500.0
        assert rx_event.size == 5

    def test_trade_event_roundtrip(self):
        """TradeイベントのPUB→SUBラウンドトリップが正しいこと"""
        port = 15556
        config = make_test_config(port)

        publisher = ZmqPublisher(config)
        publisher.start(enable_heartbeat=False)
        time.sleep(0.2)

        received_events = []

        receiver = ZmqReceiver(config)
        receiver.on_trade(lambda e: received_events.append(e))
        receiver.start(topics=[TOPIC_TRADE])
        time.sleep(0.5)

        # テストイベント送信
        event = TradeEvent(
            symbol="GCJ6.COMEX",
            action=TradeAction.EXECUTE,
            trade_id="trade456",
            aggressor_side=Side.ASK,
            price=2500.1,
            size=2,
            timestamp=1708170001.0,
        )
        publisher.enqueue_trade(event.to_json())

        time.sleep(1.0)

        receiver.stop()
        publisher.stop()

        assert len(received_events) >= 1
        rx_event = received_events[0]
        assert isinstance(rx_event, TradeEvent)
        assert rx_event.trade_id == "trade456"
        assert rx_event.aggressor_side == Side.ASK
        assert rx_event.price == 2500.1

    def test_multiple_events(self):
        """複数のイベントが順序通りに受信されること"""
        port = 15557
        config = make_test_config(port)

        publisher = ZmqPublisher(config)
        publisher.start(enable_heartbeat=False)
        time.sleep(0.2)

        mbo_events = []
        trade_events = []

        receiver = ZmqReceiver(config)
        receiver.on_mbo(lambda e: mbo_events.append(e))
        receiver.on_trade(lambda e: trade_events.append(e))
        receiver.start()  # 全トピック購読
        time.sleep(0.5)

        # 10個のMBOイベントと5個のTradeイベントを送信
        for i in range(10):
            event = MBOEvent(
                symbol="GCJ6.COMEX",
                action=MBOAction.ADD,
                order_id=f"order_{i}",
                side=Side.BID,
                price=2500.0 + i * 0.1,
                size=i + 1,
                timestamp=1000.0 + i,
            )
            publisher.enqueue_mbo(event.to_json())

        for i in range(5):
            event = TradeEvent(
                symbol="GCJ6.COMEX",
                trade_id=f"trade_{i}",
                aggressor_side=Side.BID if i % 2 == 0 else Side.ASK,
                price=2500.0,
                size=i + 1,
                timestamp=1010.0 + i,
            )
            publisher.enqueue_trade(event.to_json())

        # 受信完了待ち
        time.sleep(2.0)

        receiver.stop()
        publisher.stop()

        # 検証
        assert len(mbo_events) == 10
        assert len(trade_events) == 5

        # 注文IDの順序確認
        for i, event in enumerate(mbo_events):
            assert event.order_id == f"order_{i}"

    def test_topic_filtering(self):
        """トピックフィルタリングが正しく動作すること"""
        port = 15558
        config = make_test_config(port)

        publisher = ZmqPublisher(config)
        publisher.start(enable_heartbeat=False)
        time.sleep(0.2)

        mbo_events = []
        trade_events = []

        # MBOのみ購読するレシーバー
        receiver = ZmqReceiver(config)
        receiver.on_mbo(lambda e: mbo_events.append(e))
        receiver.on_trade(lambda e: trade_events.append(e))
        receiver.start(topics=[TOPIC_MBO])  # MBOのみ
        time.sleep(0.5)

        # MBOとTradeの両方を送信
        publisher.enqueue_mbo(MBOEvent(
            symbol="GCJ6.COMEX", order_id="m1",
            side=Side.BID, price=2500.0, size=5,
        ).to_json())
        publisher.enqueue_trade(TradeEvent(
            symbol="GCJ6.COMEX", trade_id="t1",
            aggressor_side=Side.BID, price=2500.0, size=2,
        ).to_json())

        time.sleep(1.0)

        receiver.stop()
        publisher.stop()

        # MBOのみ受信されていること
        assert len(mbo_events) >= 1
        assert len(trade_events) == 0


class TestZmqPublisherStats:
    """ZmqPublisherの統計テスト"""

    def test_stats_counting(self):
        """メッセージカウントが正しいこと"""
        port = 15559
        config = make_test_config(port)

        publisher = ZmqPublisher(config)
        publisher.start(enable_heartbeat=False)
        time.sleep(0.2)

        # メッセージ送信
        for _ in range(5):
            publisher.enqueue_mbo('{"data_type":"mbo"}')
        for _ in range(3):
            publisher.enqueue_trade('{"data_type":"trade"}')

        # キュー処理待ち
        time.sleep(1.0)

        assert publisher._stats["mbo_count"] == 5
        assert publisher._stats["trade_count"] == 3

        publisher.stop()
