import os
import sys
import logging
import threading
import queue
import time
import traceback
from typing import Optional, Dict, Any, List
try:
    import clr  # type: ignore
    _PYTHONNET_AVAILABLE = True
except ImportError:
    _PYTHONNET_AVAILABLE = False

import sysconfig
import ctypes
import platform
from pathlib import Path
from rich.console import Console

from rithmic_bridge.config import get_config
from rithmic_bridge.models import MBOEvent, TradeEvent

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

def is_pythonnet_available() -> bool:
    return _PYTHONNET_AVAILABLE

class RapiBridge:
    def __init__(self, publisher, config=None):
        self.publisher = publisher
        self.event_queue = publisher.queue if hasattr(publisher, 'queue') else None
        self.config = config or get_config()
        self.engine = None
        self.callbacks = None
        self.adm_callbacks = None
        self._connected = False
        self.wrapper_assembly = None # Added from instruction
        # 診断用カウンター
        self._dbo_count = 0
        self._trade_count = 0
        self._is_ready = threading.Event()
        self._stop_event = threading.Event()
        self.subscriptions = set() # 購読済み銘柄
        self.command_server = None
        # 口座/注文系の内部状態
        self._accounts_event = threading.Event()
        self._trade_routes_event = threading.Event()
        self._accounts_info: List[Any] = []
        self._trade_routes: List[Any] = []
        self._order_account_map: Dict[str, Any] = {}
        self._ts_login_event = threading.Event()
        self._ts_failed_event = threading.Event()
        self._last_reject_report: Dict[str, Any] = {}
        self._last_failure_report: Dict[str, Any] = {}
        self._last_alert_message: str = ""

    def _compile_wrapper(self, dll_path):
        """C#ラッパーコードを動的にコンパイルして返す"""
        try:
            from System.CodeDom.Compiler import CodeDomProvider, CompilerParameters
            
            csharp_code = """
            using System;
            using com.omnesys.rapi;

            namespace RithmicBridge.Wrapper
            {
                public class BridgeAdmCallbacks : AdmCallbacks
                {
                    public Action<AlertInfo> OnAlert;
                    public Action<EnvironmentInfo> OnEnvironment;

                    public override void Alert(AlertInfo oInfo)
                    {
                        if (OnAlert != null) OnAlert(oInfo);
                    }
                    
                    public override void EnvironmentList(EnvironmentListInfo oInfo) {}
                    
                    public override void Environment(EnvironmentInfo oInfo)
                    {
                        if (OnEnvironment != null) OnEnvironment(oInfo);
                    }

                    public override bool Equals(object obj) { return base.Equals(obj); }
                    public override int GetHashCode() { return base.GetHashCode(); }
                    public override string ToString() { return base.ToString(); }
                }

                public class BridgeRCallbacks : RCallbacks
                {
                    public bool GotAccounts = false;
                    public System.Collections.Generic.List<AccountInfo> Accounts = new System.Collections.Generic.List<AccountInfo>();

                    public Action<AlertInfo> OnAlert;
                    public Action<DboInfo> OnDbo;
                    public Action<TradeInfo> OnTradePrint;
                    public Action<OrderStatusReport> OnStatusReport;
                    public Action<OrderFillReport> OnFillReport;
                    public Action<AccountListInfo> OnAccountList;
                    public Action<TradeRouteListInfo> OnTradeRouteList;
                    public Action<OrderRejectReport> OnRejectReport;
                    public Action<OrderFailureReport> OnFailureReport;

                    public override void Alert(AlertInfo aInfo)
                    {
                        if (OnAlert != null) OnAlert(aInfo);
                    }

                    public override void Dbo(DboInfo dInfo)
                    {
                        if (OnDbo != null) OnDbo(dInfo);
                    }

                    public override void TradePrint(TradeInfo tInfo)
                    {
                        if (OnTradePrint != null) OnTradePrint(tInfo);
                    }

                    public override void StatusReport(OrderStatusReport oInfo)
                    {
                        if (OnStatusReport != null) OnStatusReport(oInfo);
                    }

                    public override void FillReport(OrderFillReport oInfo)
                    {
                        if (OnFillReport != null) OnFillReport(oInfo);
                    }

                    public override void AccountList(AccountListInfo oInfo)
                    {
                        GotAccounts = true;
                        try 
                        {
                            Accounts.Clear();
                            for(int i=0; i<oInfo.Accounts.Count; i++)
                            {
                                Accounts.Add(oInfo.Accounts[i]);
                            }
                        } 
                        catch {}

                        if (OnAccountList != null) OnAccountList(oInfo);
                    }

                    public override void TradeRouteList(TradeRouteListInfo oInfo)
                    {
                        if (OnTradeRouteList != null) OnTradeRouteList(oInfo);
                    }

                    public override void RejectReport(OrderRejectReport oInfo)
                    {
                        if (OnRejectReport != null) OnRejectReport(oInfo);
                    }

                    public override void FailureReport(OrderFailureReport oInfo)
                    {
                        if (OnFailureReport != null) OnFailureReport(oInfo);
                    }
                }

                public class BridgeRCallbacksWithAccount : BridgeRCallbacks
                {
                    // 必要に応じて拡張
                }
            }
            """
            
            provider = CodeDomProvider.CreateProvider("CSharp")
            params = CompilerParameters()
            params.GenerateInMemory = True
            params.ReferencedAssemblies.Add("System.dll")
            params.ReferencedAssemblies.Add(dll_path)
            
            results = provider.CompileAssemblyFromSource(params, csharp_code)
            
            if results.Errors.HasErrors:
                logger.error("C# Wrapper Compilation Errors:")
                for error in results.Errors:
                    logger.error(f"  {error.ErrorText}")
                return None
                
            return results.CompiledAssembly

        except Exception as e:
            logger.error(f"C# Wrapper Compilation Failed: {e}")
            logger.error(traceback.format_exc())
            return None

    def initialize_engine(self) -> bool:
        try:
            # 環境変数の設定 (Plugin Mode: Encoding=4 -> Protocol Buffers)
            # これがないと接続できない、または文字化けする可能性がある
            os.environ["RAPI_MD_ENCODING"] = "4"
            os.environ["RAPI_IH_ENCODING"] = "4"
            # TS/PnL は Encoding=4 だと接続断になる可能性があるため、デフォルト(SSL/Binary)を試す
            # os.environ["RAPI_TS_ENCODING"] = "4"
            # os.environ["RAPI_PNL_ENCODING"] = "4"
            
            logger.info("R|API+ プラグインモード用環境変数を設定しました (Encoding=4 for MD/IH/TS/PnL)")

            # --- pythonnet のプログラム内初期化 (Windows用) ---
            if sys.platform == "win32" and _PYTHONNET_AVAILABLE:
                # 既にランタイムが読み込まれているかチェック
                try:
                    import clr
                except:
                    pass
                
                # PYTHONNET_PYDLL が設定されていない場合、自動検出を試みる
                if not os.environ.get("PYTHONNET_PYDLL"):
                    python_exe = sys.executable
                    python_home = os.path.dirname(python_exe)
                    
                    # 標準的な Python DLL の名前
                    ver = "".join(platform.python_version_tuple()[:2])
                    dll_name = f"python{ver}.dll"
                    
                    search_paths = [
                        Path(python_home) / dll_name,
                        Path(python_home) / "Library" / "bin" / dll_name,  # Conda
                        Path(sysconfig.get_config_var("BINDIR") or "") / dll_name,
                    ]
                    
                    for p in search_paths:
                        if p.exists():
                            os.environ["PYTHONNET_PYDLL"] = str(p.absolute())
                            logger.info(f"PYTHONNET_PYDLL を自動設定しました: {p}")
                            break

            # --- DLL ディレクトリの特定と設定 ---
            # --- DLL ディレクトリの特定と設定 ---
            search_dirs = []
            
            # 1. R|Trader Pro デフォルトインストール先 (最優先)
            if self.config.rithmic.dll_dir:
                search_dirs.append(os.path.abspath(self.config.rithmic.dll_dir))
            
            # 標準的なインストールパス
            search_dirs.append(r"C:\Program Files (x86)\Rithmic\Rithmic Trader Pro")
            search_dirs.append(r"C:\Program Files\Rithmic\Rithmic Trader Pro")
            
            # 2. カレントディレクトリの libs (Windowsローカルの場合のみ有効)
            search_dirs.append(os.path.abspath(os.path.join(os.getcwd(), "libs")))
            
            # 3. パッケージインストールディレクトリ (これらはWSLパスになる可能性が高いので要注意)
            package_root = Path(__file__).parent.parent.parent
            search_dirs.append(os.path.abspath(package_root / "libs"))
            search_dirs.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "libs")))

            libs_dir = None
            for d in search_dirs:
                # WSLネットワークパス (\\wsl.localhost\...) は除外する
                # .NET Frameworkのセキュリティポリシー (NOT_SUPPORTED_EXCEPTION) 回避のため
                if d.startswith(r"\\wsl") or d.startswith("//wsl"):
                    logger.debug(f"WSLネットワークパスを除外します: {d}")
                    continue
                    
                if os.path.exists(d):
                    # そのディレクトリに rapiplus.dll が実際に存在するか確認
                    target_dll = os.path.join(d, "rapiplus.dll")
                    if os.path.exists(target_dll):
                        libs_dir = d
                        break
            
            if not libs_dir:
                logger.error("致命的エラー: rapiplus.dll が見つかりません。")
                logger.error("解決策:")
                logger.error("1. R|Trader Pro を Windows にインストールしてください。")
                logger.error("2. または、rapiplus.dll を含むフォルダを環境変数 RAPI_DLL_DIR に設定してください。")
                logger.error(f"探索したパス: {search_dirs}")
                return False

            # ビットネスのログ出力 (デバッグ用)
            arch = platform.architecture()[0]
            logger.info(f"Python Runtime: {arch}, DLLディレクトリを使用します: {libs_dir}")

            sys.path.append(libs_dir)
            if hasattr(os, "add_dll_directory"):
                try:
                    os.add_dll_directory(libs_dir)
                except Exception as e:
                    logger.warning(f"os.add_dll_directory に失敗しました (非致命的): {e}")

            dll_path = os.path.join(libs_dir, "rapiplus.dll")
            
            # pythonnet 3.0+ では clr.AddReference の前にランタイムを初期化しておくと安全
            try:
                clr.AddReference(dll_path)
                logger.info(f"{dll_path} をロードしました")
            except Exception as e:
                logger.error(f"DLLロード失敗: {e}")
                # 依存関係（msvcp140.dll等）が不足している可能性が高い
                return False

            import com.omnesys.rapi as rapi # type: ignore
            import System

            # C#ラッパーのコンパイル
            logger.info("C#ラッパーをコンパイル中...")
            wrapper_assembly = self._compile_wrapper(dll_path)
            if not wrapper_assembly:
                return False

            # 型の取得
            t_bridge_adm = wrapper_assembly.GetType("RithmicBridge.Wrapper.BridgeAdmCallbacks")
            t_bridge_r = wrapper_assembly.GetType("RithmicBridge.Wrapper.BridgeRCallbacks")
            t_alert_info = rapi.AlertInfo
            t_env_info = rapi.EnvironmentInfo
            t_dbo_info = rapi.DboInfo
            t_trade_info = rapi.TradeInfo
            t_order_status_report = rapi.OrderStatusReport
            t_order_fill_report = rapi.OrderFillReport
            t_account_list_info = rapi.AccountListInfo
            t_trade_route_list_info = rapi.TradeRouteListInfo
            t_order_reject_report = rapi.OrderRejectReport
            t_order_failure_report = rapi.OrderFailureReport

            # インスタンス化
            self.adm_callbacks = System.Activator.CreateInstance(t_bridge_adm)
            self.callbacks = System.Activator.CreateInstance(t_bridge_r)

            # --- コールバック関数の定義と登録 ---
            # 接続状態を個別管理
            self._states = {
                "MarketData": False,
                "TradingSystem": False,
                "History": False,
                "PnL": False
            }

            def _on_alert(info):
                msg = info.Message
                logger.warning(f"[R|API+] Alert: {msg}")
                self._last_alert_message = str(msg)
                
                # 接続成功/ログイン成功を検知 (形式: "||Login Complete|LoginComplete|MarketData|0")
                msg_lower = msg.lower()
                parts = msg.split('|')
                if len(parts) >= 6:
                    status = parts[3]
                    # "Trading System" -> "TradingSystem"
                    system = parts[4].replace(" ", "")
                    
                    if status == "LoginComplete":
                        logger.info(f"R|API+ {system} ログインが正常に完了しました")
                        self._states[system] = True
                        if system == "TradingSystem":
                            self._ts_login_event.set()
                            self._ts_failed_event.clear()
                    elif status == "ConnectionBroken":
                        logger.error(f"R|API+ {system} 接続が切れました")
                        self._states[system] = False
                        if system == "TradingSystem":
                            self._ts_failed_event.set()
                    elif status == "LoginFailed":
                        logger.error(f"R|API+ {system} ログインに失敗しました")
                        self._states[system] = False
                        if system == "TradingSystem":
                            self._ts_failed_event.set()
                elif "login complete" in msg_lower:
                    logger.info(f"R|API+ ログイン完了を検知: {msg}")
                    self._is_ready.set()

                # 人間可読メッセージ形式にも対応
                # 例:
                #   "Market Data Connection Login Complete"
                #   "Trading System Connection Broken"
                #   "Trading System Connection Login Failed"
                human_map = {
                    "market data": "MarketData",
                    "trading system": "TradingSystem",
                    "intraday history": "History",
                    "pnl": "PnL",
                }
                for key, state_name in human_map.items():
                    if key in msg_lower:
                        if "login complete" in msg_lower:
                            self._states[state_name] = True
                            if state_name == "TradingSystem":
                                self._ts_login_event.set()
                                self._ts_failed_event.clear()
                        elif "connection broken" in msg_lower or "login failed" in msg_lower:
                            self._states[state_name] = False
                            if state_name == "TradingSystem":
                                self._ts_failed_event.set()
                        elif "connection opened" in msg_lower:
                            # opened だけではログイン完了とはみなさない
                            pass
                        elif "connection closed" in msg_lower:
                            self._states[state_name] = False
                        break

                # 基本的な動作（MarketData または History）ができれば ready とする
                if self._states.get("MarketData") or self._states.get("History"):
                    if not self._is_ready.is_set():
                        logger.info("R|API+ ブリッジの基本機能が利用可能です")
                        self._is_ready.set()
                else:
                    self._is_ready.clear()
            
            def _on_environment(info):
                logger.info(f"[R|API+] Environment Found: {info.EnvKey} ({info.Description})")

            def _on_dbo(info):
                try:
                    self._dbo_count += 1
                    if self._dbo_count <= 10 or self._dbo_count % 1000 == 0:
                        logger.info(f"[R|API+] DBO Data (#{self._dbo_count}): {info.Symbol} {info.Price}@{info.Size} Side:{info.Side} Action:{info.DboUpdateType}")

                    action = str(info.DboUpdateType).lower().split('.')[-1]
                    event = MBOEvent(
                        symbol=info.Symbol,
                        action=action,
                        order_id=info.ExchOrdId,
                        side=info.Side.lower(),
                        price=info.Price,
                        size=info.Size,
                        timestamp=info.Ssboe + (info.Usecs / 1_000_000.0)
                    )
                    if self.event_queue: self.event_queue.put(event)
                    else: self.publish_to_zmq(event)
                except Exception as e:
                    logger.error(f"Dbo Error: {e}")

            def _on_trade_print(info):
                try:
                    self._trade_count += 1
                    if self._trade_count <= 10 or self._trade_count % 100 == 0:
                         logger.info(f"[R|API+] Trade Data (#{self._trade_count}): {info.Symbol} {info.Price}@{info.Size} Aggressor:{info.AggressorSide}")

                    event = TradeEvent(
                        symbol=info.Symbol,
                        trade_id=info.ExchOrdId or "",
                        aggressor_side=info.AggressorSide.lower() if info.AggressorSide else "unknown",
                        price=info.Price,
                        size=info.Size,
                        timestamp=info.Ssboe + (info.Usecs / 1_000_000.0)
                    )
                    if self.event_queue: self.event_queue.put(event)
                    else: self.publish_to_zmq(event)
                except Exception as e:
                    logger.error(f"Trade Error: {e}")

            def _on_order_change(info):
                try:
                    from rithmic_bridge.models import OrderEvent, OrderStatus
                    # 注文の状態変化を通知
                    status_map = {
                        rapi.OrderStatus.Pending: OrderStatus.PENDING,
                        rapi.OrderStatus.Open: OrderStatus.OPEN,
                        rapi.OrderStatus.Filled: OrderStatus.FILLED,
                        rapi.OrderStatus.Cancelled: OrderStatus.CANCELLED,
                        rapi.OrderStatus.Rejected: OrderStatus.REJECTED,
                    }
                    
                    status = status_map.get(info.Status, OrderStatus.OPEN)
                    
                    event = OrderEvent(
                        symbol=info.Symbol,
                        account=getattr(getattr(info, "Account", None), "AccountName", ""),
                        order_id=info.ExchOrdId or info.BasketId or "",
                        client_order_id=info.UserTag or "",
                        status=status,
                        side=info.BuySellType.lower(),
                        order_type=str(getattr(info, "Type", "market")).lower().split('.')[-1],
                        price=info.Price,
                        quantity=info.Quantity,
                        filled_quantity=info.TotalFilled,
                        avg_fill_price=info.AvgFillPrice,
                        message=getattr(info, "Text", ""),
                        timestamp=info.Ssboe + (info.Usecs / 1_000_000.0)
                    )
                    logger.info(f"[R|API+] Order Change: {event.symbol} {event.status} {event.side} {event.filled_quantity}/{event.quantity}")
                    if event.order_id and getattr(info, "Account", None):
                        self._order_account_map[event.order_id] = info.Account
                    if self.event_queue: self.event_queue.put(event)
                    else: self.publish_to_zmq(event)
                except Exception as e:
                    logger.error(f"OrderReport Error: {e}")

            def _on_order_fill(info):
                try:
                    from rithmic_bridge.models import OrderEvent, OrderStatus
                    # 約定通知
                    event = OrderEvent(
                        symbol=info.Symbol,
                        account=getattr(getattr(info, "Account", None), "AccountName", ""),
                        order_id=info.ExchOrdId or info.BasketId or "",
                        client_order_id=info.UserTag or "",
                        status=OrderStatus.PARTIAL, # 一時的にPARTIAL (後続のStatusReportで更新)
                        side=info.BuySellType.lower(),
                        price=info.FillPrice,
                        quantity=info.Quantity,
                        filled_quantity=info.FillSize, # 今回の約定数量
                        avg_fill_price=info.FillPrice,
                        timestamp=info.Ssboe + (info.Usecs / 1_000_000.0)
                    )
                    logger.info(f"[R|API+] Order Fill: {event.symbol} {event.side} {event.filled_quantity}@{event.avg_fill_price}")
                    if event.order_id and getattr(info, "Account", None):
                        self._order_account_map[event.order_id] = info.Account
                    if self.event_queue: self.event_queue.put(event)
                    else: self.publish_to_zmq(event)
                except Exception as e:
                    logger.error(f"FillReport Error: {e}")

            def _on_account_list(info):
                try:
                    count = info.Accounts.Count if info and info.Accounts else 0
                    logger.info(f"[R|API+] AccountList Received: {count} accounts")
                    self._accounts_info = []
                    if info and info.Accounts:
                        for acc in info.Accounts:
                            self._accounts_info.append(acc)
                    self._accounts_event.set()

                    from rithmic_bridge.models import AccountEvent
                    for acc in self._accounts_info:
                        account_name = getattr(acc, "AccountName", "") or getattr(acc, "AccountId", "")
                        if account_name:
                            event = AccountEvent(account_name=account_name)
                            if self.event_queue:
                                self.event_queue.put(event)
                            else:
                                self.publish_to_zmq(event)
                except Exception as e:
                    logger.error(f"AccountList Error: {e}")

            def _on_trade_route_list(info):
                try:
                    count = info.TradeRoutes.Count if info and info.TradeRoutes else 0
                    logger.info(f"[R|API+] TradeRouteList Received: {count} routes")
                    self._trade_routes = []
                    if info and info.TradeRoutes:
                        for tr in info.TradeRoutes:
                            self._trade_routes.append(tr)
                    self._trade_routes_event.set()
                except Exception as e:
                    logger.error(f"TradeRouteList Error: {e}")

            def _on_order_reject(info):
                try:
                    text = getattr(info, "Text", "") or ""
                    code = getattr(info, "ResponseCode", None)
                    completion = getattr(info, "CompletionReason", "") or ""
                    symbol = getattr(info, "Symbol", "") or ""
                    exch = getattr(info, "Exchange", "") or ""
                    rejected_size = getattr(info, "RejectedSize", None)
                    self._last_reject_report = {
                        "text": text,
                        "response_code": code,
                        "completion_reason": completion,
                        "symbol": symbol,
                        "exchange": exch,
                        "rejected_size": rejected_size,
                    }
                    logger.error(f"[R|API+] RejectReport: {self._last_reject_report}")
                except Exception as e:
                    logger.error(f"RejectReport Error: {e}")

            def _on_order_failure(info):
                try:
                    text = getattr(info, "Text", "") or ""
                    code = getattr(info, "ResponseCode", None)
                    completion = getattr(info, "CompletionReason", "") or ""
                    symbol = getattr(info, "Symbol", "") or ""
                    exch = getattr(info, "Exchange", "") or ""
                    self._last_failure_report = {
                        "text": text,
                        "response_code": code,
                        "completion_reason": completion,
                        "symbol": symbol,
                        "exchange": exch,
                    }
                    logger.error(f"[R|API+] FailureReport: {self._last_failure_report}")
                except Exception as e:
                    logger.error(f"FailureReport Error: {e}")

            # デリゲートへのバインド
            self.adm_callbacks.OnAlert = System.Action[t_alert_info](_on_alert)
            self.adm_callbacks.OnEnvironment = System.Action[t_env_info](_on_environment)
            self.callbacks.OnAlert = System.Action[t_alert_info](_on_alert)
            self.callbacks.OnDbo = System.Action[t_dbo_info](_on_dbo)
            self.callbacks.OnTradePrint = System.Action[t_trade_info](_on_trade_print)
            self.callbacks.OnStatusReport = System.Action[t_order_status_report](_on_order_change)
            self.callbacks.OnFillReport = System.Action[t_order_fill_report](_on_order_fill)
            self.callbacks.OnAccountList = System.Action[t_account_list_info](_on_account_list)
            self.callbacks.OnTradeRouteList = System.Action[t_trade_route_list_info](_on_trade_route_list)
            self.callbacks.OnRejectReport = System.Action[t_order_reject_report](_on_order_reject)
            self.callbacks.OnFailureReport = System.Action[t_order_failure_report](_on_order_failure)
            
            logger.info("コールバックのバインド完了")

            # REngineParamsの設定
            params = rapi.REngineParams()
            
            # AppName / AppVersion
            params.AppName = "RithmicBridge"
            # R|Trader Pro Version (Newer might be required)
            params.AppVersion = "17.0.0.0"
            params.AppCreateVersion = "1.0.0.0"
            # バリデーションに必要
            params.AdmCallbacks = self.adm_callbacks
            params.LogFilePath = os.path.abspath("rithmic_rapi.log")
            
            # WSL2環境の場合、WindowsホストのIPを自動検出する
            default_host = "127.0.0.1"
            if "WSL_DISTRO_NAME" in os.environ:
                try:
                    with open("/etc/resolv.conf", "r") as f:
                        for line in f:
                            if "nameserver" in line:
                                ip = line.split()[1].strip()
                                if ip != "127.0.0.1":
                                    default_host = ip
                                    break
                    logger.info(f"WSL2環境を検出しました。WindowsホストIP: {default_host}")
                except Exception as e:
                    logger.warning(f"WSL2ホストIPの自動検出に失敗しました: {e}")
            # ログファイルパスの設定
            # WSL上のパス（UNCパス）だと権限エラーになる可能性があるため、Windows側のローカルパスを優先
            win_user_profile = os.environ.get("USERPROFILE", "C:\\Users\\Akiva")
            # バックスラッシュに変換しておく
            log_path = os.path.join(win_user_profile, "rithmic_rapi.log").replace("/", "\\")
            params.LogFilePath = log_path
            
            # サーバーアドレスとドメインの設定
            # Rithmic Paper (Tokyo / AWS JP) の接続パラメータを使用する
            # 元のコードにあった設定を復元 (User ProfileのGateway: Tokyoと一致させる)
            base_addr = "ritpz15001.rithmic.com:56000"
            domain_name = "rithmic_paper_aws_jp_domain"
            
            # 各種サーバーアドレス (通常は base_addr と同じホストの異なるポート)
            params.DmnSrvrAddr = "ritpz15001.rithmic.com:65000"
            params.DomainName = domain_name
            params.LicSrvrAddr = "ritpz15001.rithmic.com:56000"
            params.LocBrokAddr = "ritpz15001.rithmic.com:64100"
            params.LoggerAddr = "ritpz15001.rithmic.com:45454"
            
            logger.info(f"REngine Params Initialized (Log: {params.LogFilePath})")

            logger.info("REngine をインスタンス化中...")
            try:
                self.engine = rapi.REngine(params)
                logger.info("REngine のインスタンス化に成功しました")
                
            except Exception as e:
                # エラー詳細の解析
                err_msg = str(e)
                if "permission error" in err_msg:
                    logger.error("権限エラー発生: R|Trader Proが起動していないか、'Allow Plug-ins'が有効になっていない、またはFirewallでブロックされています。")
                elif "bad input" in err_msg:
                    logger.error("入力エラー発生: パラメータ設定に誤りがあります。")
                
                logger.error(f"REngine インスタンス化失敗: {e}")
                logger.error(traceback.format_exc())
                return False

            return True

        except Exception as e:
            logger.error(f"R|API+ 初期化の全体エラー: {e}")
            logger.error(traceback.format_exc())
            return False

    def _get_wsl_host(self) -> Optional[str]:
        """WSL2環境においてWindowsホストのIPアドレスを取得する"""
        try:
            if not os.path.exists("/etc/resolv.conf"):
                return None
            with open("/etc/resolv.conf", "r") as f:
                for line in f:
                    if "nameserver" in line:
                        ip = line.split()[1].strip()
                        if ip != "127.0.0.1":
                            return ip
        except Exception as e:
            logger.warning(f"WSL2ホストIPの取得に失敗しました: {e}")
        return None

    def connect(self) -> bool:
        """Rithmicへのログインを実行"""
        if not self.engine:
            if not self.initialize_engine():
                return False
        
        # コマンドサーバーの開始
        if self.command_server is None:
             from .server import ZmqCommandServer
             self.command_server = ZmqCommandServer(self.config, self)
             self.command_server.start()

        try:
            import com.omnesys.rapi as rapi # type: ignore

            # ユーザー名/パスワード
            user = self.config.rithmic.user
            password = self.config.rithmic.password
            if not user or not password:
                logger.error("RITHMIC_USER / RITHMIC_PASSWORD が未設定です")
                return False

            # TS/PnLは環境差が大きいため、順に試す
            default_env_key = getattr(rapi.Constants, "DEFAULT_ENVIRONMENT_KEY", "")
            login_patterns = [
                {
                    # 公式 SampleOrder.cs の構成に寄せたパターン
                    "name": "sample-logical",
                    "md_env": "",
                    "md_addr": "login_agent_tpc",
                    "ts_env": default_env_key,
                    "ts_addr": "login_agent_opc",
                    "pnl_addr": "",
                    "ih_env": "",
                    "ih_user": "",
                    "ih_password": "",
                    "ih_addr": "",
                },
                {
                    # R|Trader Pro ローカルプラグイン向け
                    "name": "plugin-local-ts",
                    "md_env": "",
                    "md_addr": "127.0.0.1:3010",
                    "ts_env": "",
                    "ts_addr": "127.0.0.1:3011",
                    "pnl_addr": "",
                    "ih_env": "",
                    # ih_addr を使う場合は user/password も揃える
                    "ih_user": user,
                    "ih_password": password,
                    "ih_addr": "127.0.0.1:3012",
                },
                {
                    # IHを無効にした最小構成（引数バリデーション切り分け用）
                    "name": "plugin-local-minimal",
                    "md_env": "",
                    "md_addr": "127.0.0.1:3010",
                    "ts_env": "",
                    "ts_addr": "127.0.0.1:3011",
                    "pnl_addr": "",
                    "ih_env": "",
                    "ih_user": "",
                    "ih_password": "",
                    "ih_addr": "",
                },
            ]

            for idx, pattern in enumerate(login_patterns):
                self._ts_login_event.clear()
                self._ts_failed_event.clear()
                self._states["TradingSystem"] = False
                self._states["PnL"] = False

                logger.info(
                    "Connecting to Rithmic "
                    f"(pattern={pattern['name']}): "
                    f"MD={pattern['md_addr']}, TS={pattern['ts_addr']}, IH={pattern['ih_addr']}"
                )

                try:
                    self.engine.login(
                        self.callbacks,
                        pattern["md_env"],
                        user,
                        password,
                        pattern["md_addr"],
                        pattern["ts_env"],
                        user,
                        password,
                        pattern["ts_addr"],
                        pattern["pnl_addr"],
                        pattern["ih_env"],
                        pattern["ih_user"],
                        pattern["ih_password"],
                        pattern["ih_addr"],
                    )
                    logger.info("R|API+ Login Request Sent")
                except Exception as e:
                    logger.warning(
                        f"Login request rejected for pattern={pattern['name']}: {e}"
                    )
                    try:
                        self.engine.logout()
                    except Exception:
                        pass
                    if idx == len(login_patterns) - 1:
                        break
                    time.sleep(1.0)
                    continue

                # TSの成否を短時間待って判定（MDだけ成功するケースを切り分ける）
                start = time.time()
                while time.time() - start < 8.0:
                    if self._ts_login_event.is_set():
                        self._connected = True
                        logger.info(f"TradingSystem connected using pattern={pattern['name']}")
                        return True
                    if self._ts_failed_event.is_set():
                        break
                    time.sleep(0.1)

                logger.warning(f"TradingSystem not established with pattern={pattern['name']}")

                # TS未接続でも、MD/Historyが利用可能なら配信専用モードとして継続する
                if self._states.get("MarketData") or self._states.get("History"):
                    self._connected = True
                    logger.warning(
                        "TradingSystem is unavailable, but MarketData/History is available. "
                        "Continue in market-data-only mode."
                    )
                    return True

                try:
                    self.engine.logout()
                except Exception:
                    pass

                # 最後のパターンで失敗なら終了
                if idx == len(login_patterns) - 1:
                    break
                # 次の試行前に少し待つ
                time.sleep(1.0)

            logger.error("TradingSystem connection failed in all login patterns")
            self._connected = False
            return False

        except Exception as e:
            logger.error(f"接続失敗: {e}")
            logger.error(traceback.format_exc())
            return False

    def subscribe(self, symbol=None, exchange=None, timeout=30) -> bool:
        if not self._connected or not self.engine: return False
        
        # R|API+ の内部状態が安定するまで少し待機
        logger.info("R|API+ 状態安定化のため2秒間待機します...")
        time.sleep(2.0)

        # ログイン完了を待機 (no handle エラー防止)
        # 各接続の状態を個別に待機する
        required_states = ["MarketData", "TradingSystem", "History", "PnL"]
        for state_name in required_states:
            if not self._states.get(state_name):
                logger.info(f"R|API+ の {state_name} 接続完了を待機します (最大 {timeout}秒)...")
                # _is_ready は全体ではなく、各状態のイベントを待つべき
                # ここでは簡略化のため、全体がreadyになるのを待つ
                # 厳密には_statesディクショナリの各エントリがTrueになるのを待つ
                start_time = time.time()
                while not self._states.get(state_name) and (time.time() - start_time) < timeout:
                    time.sleep(0.1)
                if not self._states.get(state_name):
                    logger.warning(f"R|API+ の {state_name} 接続完了を確認できませんでした。処理を強制続行します。")
                else:
                    logger.info(f"R|API+ {state_name} 接続完了を確認しました。")

        if not all(self._states.get(s) for s in required_states):
            logger.warning("R|API+ の全ての接続が完了していません。購読処理を続行します。")
        else:
            logger.info("R|API+ の全ての接続が完了しました。購読を開始します。")

        try:
            import com.omnesys.rapi as rapi # type: ignore
            import System # type: ignore
            
            target_symbol = symbol or self.config.rithmic.symbol
            target_exchange = exchange or self.config.rithmic.exchange
            
            # 購読フラグの取得 (All または明示的なフラグ)
            try:
                flags = rapi.SubscriptionFlags.All
            except:
                # 明示的に定義 (Prints|Quotes|Best|Close|Settlement|Open|MarketMode|HighLow|TradeVolume)
                flags = 0x01 | 0x02 | 0x04 | 0x08 | 0x20 | 0x40 | 0x80 | 0x100 | 0x200
            
            logger.info(f"購読リクエスト: {target_symbol}@{target_exchange} (Flags: {flags})")
            # 通常の市場データ購読
            self.engine.subscribe(target_exchange, target_symbol, flags, None)
            
            # MBO (DBO) 購読
            # 全価格レベルを対象とするため NaN を指定
            logger.info(f"DBO購読リクエスト: {target_symbol}@{target_exchange}")
            self.engine.subscribeDbo(target_exchange, target_symbol, System.Double.NaN, None)
            
            # DBOブックの初期構築リクエスト
            logger.info(f"DBOブック再構築リクエスト: {target_symbol}@{target_exchange}")
            self.engine.rebuildDboBook(target_exchange, target_symbol, System.Double.NaN, None)
            
            return True
        except Exception as e:
            logger.error(f"購読エラー: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return False

    def disconnect(self):
        if self.engine:
            try:
                self.engine.logout()
            except: pass
            self._connected = False

        if self.command_server:
            self.command_server.stop()

    def add_subscription(self, symbol: str, exchange: str) -> bool:
        """動的に銘柄を購読する"""
        if not self.engine or not self._connected:
            logger.error("R|API+未接続のため購読できません")
            return False
            
        full_symbol = f"{symbol}.{exchange}"
        if full_symbol in self.subscriptions:
            logger.info(f"既に購読済みです: {full_symbol}")
            return True
            
        try:
            # MD/DBO購読
            # self.subscribeの中でMD購読とDBO購読の両方を行っているためこれだけでOK
            self.subscribe(symbol, exchange)
            
            self.subscriptions.add(full_symbol)
            logger.info(f"追加購読成功: {full_symbol}")
            return True
        except Exception as e:
            logger.error(f"追加購読エラー: {e}")
            return False

    def get_subscriptions(self) -> List[str]:
        """現在の購読リストを返す"""
        return list(self.subscriptions)

    def get_accounts(self) -> List[str]:
        if not self.engine or not self._connected:
            return []
        
        # Trading System の接続完了を待つが、失敗しても試行する
        for i in range(5):
            if self._states.get("TradingSystem"):
                break
            time.sleep(1.0)
        
        # R|API+ では、Login後に自動的に AccountList コールバックが呼ばれる仕様。
        # REngine.getAccounts() を呼び出す必要はなく（あるいは引数が必要で不明なため）、
        # 既に受信済みのキャッシュ（self.accounts）を返すのが正解。
        # もし未受信なら少し待機する。
        
        try:
            # 明示的に口座一覧取得を要求（callbackで受ける）
            self._accounts_event.clear()
            try:
                self.engine.getAccounts("")
            except TypeError:
                # 実装差分で引数なし版のみ提供される場合に備える
                self.engine.getAccounts()
        except Exception as e:
            logger.warning(f"getAccounts 呼び出しに失敗（callback待機にフォールバック）: {e}")

        if not self._accounts_event.wait(timeout=5.0):
            logger.warning("AccountList callback not received yet")
            return []

        account_names: List[str] = []
        for acc in self._accounts_info:
            name = getattr(acc, "AccountName", "") or getattr(acc, "AccountId", "")
            if name:
                account_names.append(name)

        logger.info(f"R|API+ 口座一覧取得成功: {account_names}")
        return account_names

    def _resolve_account_info(self, account: str) -> Optional[Any]:
        """口座名またはIDから AccountInfo を解決する"""
        for acc in self._accounts_info:
            if getattr(acc, "AccountName", None) == account or getattr(acc, "AccountId", None) == account:
                return acc
        return None

    def _select_trade_route(self, account_info: Any, exchange: str) -> Optional[str]:
        """
        口座/取引所に合う稼働中の trade route を選ぶ
        SampleOrder.cs の選定ロジックに合わせる。
        """
        if not self.engine:
            return None

        self._trade_routes_event.clear()
        self._trade_routes = []
        self.engine.listTradeRoutes(None)
        if not self._trade_routes_event.wait(timeout=5.0):
            logger.warning("TradeRouteList callback not received")
            return None

        account_fcm = getattr(account_info, "FcmId", None)
        account_ib = getattr(account_info, "IbId", None)
        target_exchange = (exchange or "").upper()

        # 定数が取得できる場合は優先利用
        try:
            import com.omnesys.rapi as rapi # type: ignore
            status_up = getattr(rapi.Constants, "TRADE_ROUTE_STATUS_UP", "up")
        except Exception:
            status_up = "up"

        for route in self._trade_routes:
            route_fcm = getattr(route, "FcmId", None)
            route_ib = getattr(route, "IbId", None)
            route_exchange = str(getattr(route, "Exchange", "")).upper()
            route_status = str(getattr(route, "Status", ""))
            if (
                route_fcm == account_fcm
                and route_ib == account_ib
                and route_exchange == target_exchange
                and (route_status == status_up or route_status.lower() == "up")
            ):
                selected = getattr(route, "TradeRoute", None)
                if selected:
                    logger.info(f"TradeRoute selected: {selected} ({route_exchange})")
                    return selected

        logger.warning(
            "利用可能なTradeRouteが見つかりませんでした "
            f"(exchange={exchange}, fcm={account_fcm}, ib={account_ib})"
        )
        return None

    def place_order(self, account: str, symbol: str, exchange: str, quantity: int, price: float, side: str, order_type: str = "market") -> Dict[str, Any]:
        """
        新規注文を出す
        """
        if not self.engine or not self._connected:
            return {"status": "error", "message": "R|API+ not connected"}

        try:
            import com.omnesys.rapi as rapi # type: ignore
            
            if not self._states.get("TradingSystem"):
                return {
                    "status": "error",
                    "message": "TradingSystem is not connected. Check plugin/account permission for order routing.",
                }

            # 口座情報を最新化
            self.get_accounts()

            # 口座オブジェクトの取得
            acc_info = self._resolve_account_info(account)
            if not acc_info:
                return {"status": "error", "message": f"Account not found: {account}"}

            # 注文イベント受信用に口座購読を有効化
            self.engine.subscribeOrder(acc_info)

            # 取引ルートの取得/選択（RAPI 6.1以降は必須）
            trade_route = self._select_trade_route(acc_info, exchange)
            if not trade_route:
                return {
                    "status": "error",
                    "message": f"No available trade route for {exchange}. Account may not have execution permission.",
                }

            # 注文種別に応じたパラメータクラスの選択
            if order_type.lower() == "market":
                params = rapi.MarketOrderParams()
            elif order_type.lower() == "limit":
                params = rapi.LimitOrderParams()
                params.Price = float(price)
            else:
                # Stopなどは個別のクラスがある
                return {"status": "error", "message": f"Unsupported order type for now: {order_type}"}

            params.Account = acc_info
            params.Symbol = symbol
            params.Exchange = exchange
            params.Qty = int(quantity)
            params.BuySellType = (
                rapi.Constants.BUY_SELL_TYPE_BUY
                if side.lower() == "buy"
                else rapi.Constants.BUY_SELL_TYPE_SELL
            )
            params.Duration = rapi.Constants.ORDER_DURATION_DAY
            params.EntryType = rapi.Constants.ORDER_ENTRY_TYPE_MANUAL
            params.TradeRoute = trade_route
            params.Tag = f"rb-{int(time.time())}"
            params.UserMsg = None
            
            logger.info(
                f"発注リクエスト: {side} {quantity} {symbol}@{exchange} "
                f"(type={order_type}, route={trade_route})"
            )
            
            # 発注実行 (.NETでは sendOrder メソッド)
            self.engine.sendOrder(params)
            
            return {
                "status": "ok",
                "order_id": params.Tag, # 非同期に実IDが返る。ここではTagを返す
                "message": "Order sent"
            }
            
        except Exception as e:
            logger.error(f"発注エラー: {e}")
            return {"status": "error", "message": str(e)}

    def cancel_order(self, order_id: str) -> Dict[str, Any]:
        """
        注文をキャンセルする
        """
        if not self.engine or not self._connected:
            return {"status": "error", "message": "R|API+ not connected"}
            
        try:
            import com.omnesys.rapi as rapi # type: ignore

            acc_info = self._order_account_map.get(order_id)
            if not acc_info:
                # フォールバック: 口座が1件だけならその口座で試みる
                self.get_accounts()
                if len(self._accounts_info) == 1:
                    acc_info = self._accounts_info[0]
                    logger.warning(
                        f"order_id={order_id} の口座対応が不明のため、唯一の口座でキャンセルを試みます"
                    )
                else:
                    return {
                        "status": "error",
                        "message": "Account context is required for cancelOrder and could not be resolved.",
                    }

            self.engine.cancelOrder(
                acc_info,
                order_id,
                rapi.Constants.ORDER_ENTRY_TYPE_MANUAL,
                "",
                "",
                "",
                None,
            )

            return {"status": "ok", "message": "Cancel requested"}
        except Exception as e:
            logger.error(f"キャンセルエラー: {e}")
            return {"status": "error", "message": str(e)}

    def diagnose_trading(self, account: Optional[str] = None, exchange: Optional[str] = None) -> Dict[str, Any]:
        """
        注文系の実行可否を切り分けるための診断情報を返す。
        """
        result: Dict[str, Any] = {
            "status": "ok",
            "connected": bool(self._connected),
            "states": dict(getattr(self, "_states", {})),
            "last_alert": self._last_alert_message,
            "last_reject_report": dict(self._last_reject_report),
            "last_failure_report": dict(self._last_failure_report),
            "accounts": [],
            "trade_routes": [],
            "requested": {"account": account, "exchange": exchange},
            "selected_trade_route": None,
            "likely_permission_issue": False,
            "message": "",
        }

        if not self.engine or not self._connected:
            result["status"] = "error"
            result["message"] = "R|API+ not connected"
            return result

        # 口座一覧を更新
        self.get_accounts()
        for acc in self._accounts_info:
            result["accounts"].append({
                "account_name": getattr(acc, "AccountName", "") or "",
                "account_id": getattr(acc, "AccountId", "") or "",
                "fcm_id": getattr(acc, "FcmId", "") or "",
                "ib_id": getattr(acc, "IbId", "") or "",
            })

        # TradeRoute一覧を更新
        try:
            self._trade_routes_event.clear()
            self._trade_routes = []
            self.engine.listTradeRoutes(None)
            if self._trade_routes_event.wait(timeout=5.0):
                for route in self._trade_routes:
                    result["trade_routes"].append({
                        "trade_route": getattr(route, "TradeRoute", "") or "",
                        "exchange": getattr(route, "Exchange", "") or "",
                        "status": str(getattr(route, "Status", "")),
                        "fcm_id": getattr(route, "FcmId", "") or "",
                        "ib_id": getattr(route, "IbId", "") or "",
                        "is_default": bool(getattr(route, "Default", False)),
                    })
        except Exception as e:
            result["message"] = f"listTradeRoutes failed: {e}"

        # 指定口座・取引所がある場合は route 選択可否を評価
        target_account = account
        if not target_account and len(self._accounts_info) == 1:
            target_account = getattr(self._accounts_info[0], "AccountName", None)

        if target_account and exchange:
            acc_info = self._resolve_account_info(target_account)
            if acc_info:
                selected = self._select_trade_route(acc_info, exchange)
                result["selected_trade_route"] = selected
                if not selected and self._states.get("TradingSystem") and len(self._accounts_info) > 0:
                    result["likely_permission_issue"] = True
            else:
                result["message"] = f"Account not found: {target_account}"
        elif self._states.get("TradingSystem") and len(self._accounts_info) > 0 and len(result["trade_routes"]) == 0:
            # TS接続済みで口座があるのにrouteが無い場合は権限不足の可能性が高い
            result["likely_permission_issue"] = True

        if not result["message"]:
            if not self._states.get("TradingSystem"):
                result["message"] = "TradingSystem is not connected"
            elif len(result["accounts"]) == 0:
                result["message"] = "No accounts returned (permission or login context issue)"
            elif result["likely_permission_issue"]:
                result["message"] = "No executable trade route was found for the account/exchange"
            else:
                result["message"] = "Diagnostics collected"

        return result

    def publish_to_zmq(self, event: Any):
        """イベントをトピックに応じてパブリッシャーに送る"""
        from rithmic_bridge.models import MBOEvent, TradeEvent, OrderEvent, AccountEvent
        if isinstance(event, MBOEvent):
            self.publisher.enqueue_mbo(event.to_json())
        elif isinstance(event, TradeEvent):
            self.publisher.enqueue_trade(event.to_json())
        elif isinstance(event, OrderEvent):
            self.publisher.enqueue(b"order", event.to_json())
        elif isinstance(event, AccountEvent):
            self.publisher.enqueue(b"account", event.to_json())

    def stop(self): self.disconnect()
    @property
    def is_connected(self) -> bool: return self._connected
