"""
データモデルのユニットテスト

MBOEvent/TradeEventのシリアライズ/デシリアライズ、
parse_event関数のテスト。
"""

import json
import time

import pytest

from rithmic_bridge.models import (
    MBOEvent, TradeEvent,
    DataType, MBOAction, Side, TradeAction,
    parse_event,
    TOPIC_MBO, TOPIC_TRADE, TOPIC_ALL,
)


class TestMBOEvent:
    """MBOEventのテスト"""

    def test_create_default(self):
        """デフォルト値でMBOEventを作成できること"""
        event = MBOEvent(symbol="GCJ6.COMEX")
        assert event.symbol == "GCJ6.COMEX"
        assert event.data_type == DataType.MBO
        assert event.action == MBOAction.ADD
        assert event.timestamp > 0  # 自動設定されること

    def test_create_with_values(self):
        """指定した値でMBOEventを作成できること"""
        ts = 1708170000.123456
        event = MBOEvent(
            symbol="GCJ6.COMEX",
            action=MBOAction.MODIFY,
            order_id="12345",
            side=Side.ASK,
            price=2500.1,
            size=5,
            timestamp=ts,
        )
        assert event.action == MBOAction.MODIFY
        assert event.order_id == "12345"
        assert event.side == Side.ASK
        assert event.price == 2500.1
        assert event.size == 5
        assert event.timestamp == ts

    def test_to_json(self):
        """JSON文字列に変換できること"""
        event = MBOEvent(
            symbol="GCJ6.COMEX",
            action=MBOAction.ADD,
            order_id="123",
            side=Side.BID,
            price=2500.0,
            size=3,
            timestamp=1708170000.0,
        )
        json_str = event.to_json()
        data = json.loads(json_str)
        assert data["symbol"] == "GCJ6.COMEX"
        assert data["data_type"] == "mbo"
        assert data["action"] == "add"
        assert data["order_id"] == "123"
        assert data["side"] == "bid"
        assert data["price"] == 2500.0
        assert data["size"] == 3

    def test_from_json(self):
        """JSON文字列からMBOEventを復元できること"""
        original = MBOEvent(
            symbol="GCJ6.COMEX",
            action=MBOAction.CANCEL,
            order_id="456",
            side=Side.ASK,
            price=2501.0,
            size=2,
            timestamp=1708170001.0,
        )
        json_str = original.to_json()
        restored = MBOEvent.from_json(json_str)
        assert restored.symbol == original.symbol
        assert restored.action == original.action
        assert restored.order_id == original.order_id
        assert restored.side == original.side
        assert restored.price == original.price
        assert restored.size == original.size

    def test_to_dict_and_from_dict(self):
        """辞書への変換と復元ができること"""
        original = MBOEvent(
            symbol="GCJ6.COMEX",
            order_id="789",
            side=Side.BID,
            price=2500.5,
            size=10,
            timestamp=1708170002.0,
        )
        d = original.to_dict()
        restored = MBOEvent.from_dict(d)
        assert restored.order_id == original.order_id
        assert restored.price == original.price


class TestTradeEvent:
    """TradeEventのテスト"""

    def test_create_default(self):
        """デフォルト値でTradeEventを作成できること"""
        event = TradeEvent(symbol="GCJ6.COMEX")
        assert event.symbol == "GCJ6.COMEX"
        assert event.data_type == DataType.TRADE
        assert event.action == TradeAction.EXECUTE
        assert event.timestamp > 0

    def test_create_with_values(self):
        """指定した値でTradeEventを作成できること"""
        event = TradeEvent(
            symbol="GCJ6.COMEX",
            trade_id="987654",
            aggressor_side=Side.ASK,
            price=2500.1,
            size=2,
            timestamp=1708170000.123500,
        )
        assert event.trade_id == "987654"
        assert event.aggressor_side == Side.ASK
        assert event.price == 2500.1
        assert event.size == 2

    def test_json_roundtrip(self):
        """JSON変換のラウンドトリップが正しいこと"""
        original = TradeEvent(
            symbol="GCJ6.COMEX",
            trade_id="111",
            aggressor_side=Side.BID,
            price=2499.9,
            size=5,
            timestamp=1708170003.0,
        )
        json_str = original.to_json()
        restored = TradeEvent.from_json(json_str)
        assert restored.trade_id == original.trade_id
        assert restored.aggressor_side == original.aggressor_side
        assert restored.price == original.price
        assert restored.size == original.size


class TestParseEvent:
    """parse_event関数のテスト"""

    def test_parse_mbo_event(self):
        """MBOイベントのJSONを正しくパースできること"""
        json_str = json.dumps({
            "symbol": "GCJ6.COMEX",
            "data_type": "mbo",
            "action": "add",
            "order_id": "123",
            "side": "bid",
            "price": 2500.0,
            "size": 3,
            "timestamp": 1708170000.0,
        })
        event = parse_event(json_str)
        assert isinstance(event, MBOEvent)
        assert event.order_id == "123"

    def test_parse_trade_event(self):
        """TradeイベントのJSONを正しくパースできること"""
        json_str = json.dumps({
            "symbol": "GCJ6.COMEX",
            "data_type": "trade",
            "action": "execute",
            "trade_id": "456",
            "aggressor_side": "ask",
            "price": 2500.0,
            "size": 2,
            "timestamp": 1708170000.0,
        })
        event = parse_event(json_str)
        assert isinstance(event, TradeEvent)
        assert event.trade_id == "456"
        assert event.aggressor_side == Side.ASK

    def test_parse_unknown_type_raises(self):
        """不明なdata_typeでValueErrorが発生すること"""
        json_str = json.dumps({
            "symbol": "GCJ6.COMEX",
            "data_type": "unknown",
        })
        with pytest.raises(ValueError, match="不明なdata_type"):
            parse_event(json_str)


class TestTopicConstants:
    """トピック定数のテスト"""

    def test_topic_types(self):
        """トピック定数がbytes型であること"""
        assert isinstance(TOPIC_MBO, bytes)
        assert isinstance(TOPIC_TRADE, bytes)
        assert isinstance(TOPIC_ALL, bytes)

    def test_topic_values(self):
        """トピック定数の値が正しいこと"""
        assert TOPIC_MBO == b"mbo"
        assert TOPIC_TRADE == b"trade"
        assert TOPIC_ALL == b""
