"""
データモデル定義モジュール

MBOイベントとTradeイベントのデータクラスを定義する。
ZeroMQ経由で転送するメッセージのJSON形式を規定し、
シリアライズ/デシリアライズ処理を提供する。
"""

import json
import time
from dataclasses import dataclass, field, asdict
from enum import Enum
from typing import Optional, Union


class DataType(str, Enum):
    """データタイプの識別子"""
    MBO = "mbo"             # Market By Order (板情報の個別注文レベル)
    TRADE = "trade"         # 約定履歴 (Trade Print)
    HEARTBEAT = "heartbeat" # Heartbeat (生存確認 & レイテンシ計測)


class MBOAction(str, Enum):
    """MBOイベントのアクション種別 (Rithmic DboUpdateType に準拠)"""
    ADD = "new"         # 新規注文の追加
    MODIFY = "change"    # 既存注文の変更
    CANCEL = "delete"    # 注文のキャンセル
    IMAGE = "image"      # 板情報の初期スナップショット
    RESET = "reset"      # リセット
    CLEAR = "clear"      # 板情報のクリア


class Side(str, Enum):
    """注文の方向 (Rithmic に準拠)"""
    BID = "b"   # 買い注文 (Bid)
    ASK = "s"   # 売り注文 (Ask)


class TradeAction(str, Enum):
    """Tradeイベントのアクション種別"""
    EXECUTE = "execute"  # 約定


@dataclass
class MBOEvent:
    """
    MBO（Market By Order）イベント

    板情報の個別注文レベルの更新イベントを表す。
    各注文にはユニークなorder_idが割り当てられ、
    add/modify/cancelのアクションで板の状態が変化する。
    """
    symbol: str                    # シンボル名 (例: GCJ6.COMEX)
    data_type: str = DataType.MBO  # データタイプ識別子
    action: str = MBOAction.ADD    # アクション (add/modify/cancel)
    order_id: str = ""             # 注文のユニークID
    side: str = Side.BID           # 方向 (bid/ask)
    price: float = 0.0             # 価格
    size: int = 0                  # 数量
    timestamp: float = 0.0         # タイムスタンプ (Unix epoch, マイクロ秒精度)

    def __post_init__(self):
        """タイムスタンプが未設定の場合は現在時刻を設定"""
        if self.timestamp == 0.0:
            self.timestamp = time.time()

    def to_json(self) -> str:
        """JSON文字列に変換"""
        return json.dumps(asdict(self), ensure_ascii=False)

    def to_dict(self) -> dict:
        """辞書に変換"""
        return asdict(self)

    @classmethod
    def from_json(cls, json_str: str) -> "MBOEvent":
        """JSON文字列からMBOEventを生成"""
        data = json.loads(json_str)
        return cls(**data)

    @classmethod
    def from_dict(cls, data: dict) -> "MBOEvent":
        """辞書からMBOEventを生成"""
        return cls(**data)


@dataclass
class TradeEvent:
    """
    約定（Trade Print）イベント

    実際に約定した取引のイベントを表す。
    aggressor_sideはテイカー（成行をぶつけた側）の方向を示し、
    Order Flow分析において最も重要な特徴量の一つ。
    """
    symbol: str                          # シンボル名 (例: GCJ6.COMEX)
    data_type: str = DataType.TRADE      # データタイプ識別子
    action: str = TradeAction.EXECUTE    # アクション (execute)
    trade_id: str = ""                   # 約定のユニークID
    aggressor_side: str = Side.BID       # テイカーの方向 (bid=買い成行/ask=売り成行)
    price: float = 0.0                   # 約定価格
    size: int = 0                        # 約定数量
    timestamp: float = 0.0               # タイムスタンプ (Unix epoch, マイクロ秒精度)

    def __post_init__(self):
        """タイムスタンプが未設定の場合は現在時刻を設定"""
        if self.timestamp == 0.0:
            self.timestamp = time.time()

    def to_json(self) -> str:
        """JSON文字列に変換"""
        return json.dumps(asdict(self), ensure_ascii=False)

    def to_dict(self) -> dict:
        """辞書に変換"""
        return asdict(self)

    @classmethod
    def from_json(cls, json_str: str) -> "TradeEvent":
        """JSON文字列からTradeEventを生成"""
        data = json.loads(json_str)
        return cls(**data)

    @classmethod
    def from_dict(cls, data: dict) -> "TradeEvent":
        """辞書からTradeEventを生成"""
        return cls(**data)


@dataclass
class HeartbeatEvent:
    """
    Heartbeat（生存確認）イベント

    パブリッシャーが定期的に送信する生存確認メッセージ。
    受信側でレイテンシ計測にも使用する。
    publisher_tsにパブリッシャー側のタイムスタンプを格納し、
    受信側で受信時刻との差分をレイテンシとして算出する。
    """
    data_type: str = DataType.HEARTBEAT  # データタイプ識別子
    publisher_ts: float = 0.0            # パブリッシャー側のタイムスタンプ
    seq: int = 0                         # シーケンス番号（パケットロス検出用）
    pub_mbo_count: int = 0               # パブリッシャー側のMBO送信数
    pub_trade_count: int = 0             # パブリッシャー側のTrade送信数

    def __post_init__(self):
        """タイムスタンプが未設定の場合は現在時刻を設定"""
        if self.publisher_ts == 0.0:
            self.publisher_ts = time.time()

    def to_json(self) -> str:
        """JSON文字列に変換"""
        return json.dumps(asdict(self), ensure_ascii=False)

    def to_dict(self) -> dict:
        """辞書に変換"""
        return asdict(self)

    @classmethod
    def from_json(cls, json_str: str) -> "HeartbeatEvent":
        """JSON文字列からHeartbeatEventを生成"""
        data = json.loads(json_str)
        return cls(**data)

    @classmethod
    def from_dict(cls, data: dict) -> "HeartbeatEvent":
        """辞書からHeartbeatEventを生成"""
        return cls(**data)


def parse_event(json_str: str) -> Union[MBOEvent, TradeEvent, HeartbeatEvent]:
    """
    JSON文字列をイベントオブジェクトにパースする。

    data_typeフィールドに基づいて適切なイベントクラスに変換する。

    Args:
        json_str: パースするJSON文字列

    Returns:
        MBOEvent, TradeEvent, または HeartbeatEvent のインスタンス

    Raises:
        ValueError: 不明なdata_typeの場合
    """
    data = json.loads(json_str)
    data_type = data.get("data_type", "")

    if data_type == DataType.MBO:
        return MBOEvent.from_dict(data)
    elif data_type == DataType.TRADE:
        return TradeEvent.from_dict(data)
    elif data_type == DataType.HEARTBEAT:
        return HeartbeatEvent.from_dict(data)
    else:
        raise ValueError(f"不明なdata_type: {data_type}")


# ZeroMQ トピックプレフィックス定数
# トピックフィルタリングに使用
TOPIC_MBO = b"mbo"            # MBOイベント用トピック
TOPIC_TRADE = b"trade"         # Tradeイベント用トピック
TOPIC_HEARTBEAT = b"heartbeat" # Heartbeat用トピック
TOPIC_ALL = b""                # 全イベント受信用（フィルタなし）
