"""
ダミーデータジェネレーターモジュール

R|API+が利用できない環境でのテスト用として、
現実的なMBOイベントとTradeイベントを生成する。

金先物（GC）の典型的な価格帯・注文サイズを模擬し、
板の形成・変更・キャンセル、および約定のパターンを再現する。
"""

import logging
import random
import threading
import time
from typing import Optional

from rich.console import Console

from ..config import BridgeConfig
from ..models import (
    MBOEvent, TradeEvent,
    DataType, MBOAction, Side, TradeAction,
    TOPIC_MBO, TOPIC_TRADE,
)
from .server import ZmqPublisher

logger = logging.getLogger(__name__)
console = Console()


class DummyDataGenerator:
    """
    テスト用のダミーMBO/Tradeデータジェネレーター。

    金先物（GC）の板情報と約定データを模擬的に生成し、
    ZmqPublisherのキューに投入する。
    """

    def __init__(
        self,
        publisher: ZmqPublisher,
        config: BridgeConfig,
        events_per_second: int = 100,
    ):
        """
        Args:
            publisher: ZeroMQパブリッシャー
            config: ブリッジ設定
            events_per_second: 1秒あたりの生成イベント数
        """
        self.publisher = publisher
        self.config = config
        self.events_per_second = events_per_second
        self._thread: Optional[threading.Thread] = None
        self._running = threading.Event()

        # シンボル名
        self._symbol = config.get_symbol_full()

        # 板状態のシミュレーション用
        self._base_price = 2500.0    # 基準価格 (GCの典型的な価格帯)
        self._tick_size = 0.1        # ティックサイズ
        self._next_order_id = 1      # 注文ID連番
        self._next_trade_id = 1      # 約定ID連番

        # 現在の板にある注文ID管理 (cancel/modify用)
        self._active_orders: dict[str, dict] = {}

    def start(self) -> None:
        """データ生成を開始する"""
        if self._running.is_set():
            logger.warning("ダミーデータジェネレーターは既に実行中です")
            return

        self._running.set()
        self._thread = threading.Thread(
            target=self._generate_loop,
            name="dummy-generator",
            daemon=True,
        )
        self._thread.start()

        console.print(
            f"[bold cyan]▶ ダミーデータ生成開始[/] - "
            f"{self.events_per_second} events/s, "
            f"シンボル: {self._symbol}",
        )

    def stop(self) -> None:
        """データ生成を停止する"""
        if not self._running.is_set():
            return

        self._running.clear()
        if self._thread and self._thread.is_alive():
            self._thread.join(timeout=5.0)

        console.print("[bold yellow]■ ダミーデータ生成停止[/]")

    def _generate_loop(self) -> None:
        """
        データ生成ループ。

        MBOイベントとTradeイベントを指定レートで生成し、
        パブリッシャーのキューに投入する。
        """
        interval = 1.0 / self.events_per_second
        logger.info(
            f"ダミーデータ生成ループ開始 - "
            f"インターバル: {interval*1000:.2f}ms"
        )

        while self._running.is_set():
            try:
                # MBOイベントとTradeイベントの比率 (約8:2)
                if random.random() < 0.8:
                    self._generate_mbo_event()
                else:
                    self._generate_trade_event()

                # レート制御
                time.sleep(interval)

            except Exception as e:
                logger.error(f"ダミーデータ生成エラー: {e}")
                time.sleep(0.01)

    def _generate_mbo_event(self) -> None:
        """MBOイベントを生成してキューに投入する"""
        # アクションの選択（既存注文がある場合はmodify/cancelの確率を上げる）
        if self._active_orders and random.random() < 0.5:
            # 既存注文の変更またはキャンセル
            order_id = random.choice(list(self._active_orders.keys()))
            order_info = self._active_orders[order_id]

            if random.random() < 0.5:
                # modify: サイズ変更
                new_size = max(1, order_info["size"] + random.randint(-3, 3))
                event = MBOEvent(
                    symbol=self._symbol,
                    action=MBOAction.MODIFY,
                    order_id=order_id,
                    side=order_info["side"],
                    price=order_info["price"],
                    size=new_size,
                )
                self._active_orders[order_id]["size"] = new_size
            else:
                # cancel: 注文取消
                event = MBOEvent(
                    symbol=self._symbol,
                    action=MBOAction.CANCEL,
                    order_id=order_id,
                    side=order_info["side"],
                    price=order_info["price"],
                    size=order_info["size"],
                )
                del self._active_orders[order_id]
        else:
            # add: 新規注文
            order_id = str(self._next_order_id)
            self._next_order_id += 1

            side = random.choice([Side.BID, Side.ASK])
            # bidは基準価格以下、askは基準価格以上
            if side == Side.BID:
                offset = -random.randint(1, 50) * self._tick_size
            else:
                offset = random.randint(1, 50) * self._tick_size

            price = round(self._base_price + offset, 1)
            size = random.randint(1, 20)

            event = MBOEvent(
                symbol=self._symbol,
                action=MBOAction.ADD,
                order_id=order_id,
                side=side,
                price=price,
                size=size,
            )

            # アクティブ注文に追加
            self._active_orders[order_id] = {
                "side": side,
                "price": price,
                "size": size,
            }

            # アクティブ注文が多すぎる場合は古いものをキャンセル
            if len(self._active_orders) > 200:
                oldest_id = list(self._active_orders.keys())[0]
                del self._active_orders[oldest_id]

        # キューに投入
        self.publisher.enqueue_mbo(event.to_json())

    def _generate_trade_event(self) -> None:
        """Tradeイベントを生成してキューに投入する"""
        trade_id = str(self._next_trade_id)
        self._next_trade_id += 1

        # テイカー方向（買い成行 or 売り成行）
        aggressor_side = random.choice([Side.BID, Side.ASK])

        # 約定価格: 基準価格の微小変動
        price_offset = random.randint(-5, 5) * self._tick_size
        price = round(self._base_price + price_offset, 1)

        # 約定数量
        size = random.randint(1, 10)

        # 基準価格を約定価格方向に微調整（トレンド模倣）
        self._base_price += price_offset * 0.01

        event = TradeEvent(
            symbol=self._symbol,
            action=TradeAction.EXECUTE,
            trade_id=trade_id,
            aggressor_side=aggressor_side,
            price=price,
            size=size,
        )

        # キューに投入
        self.publisher.enqueue_trade(event.to_json())

    @property
    def is_running(self) -> bool:
        """ジェネレーターが実行中かどうか"""
        return self._running.is_set()
