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

    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 Action<AlertInfo> OnAlert;
                    public Action<DboInfo> OnDbo;
                    public Action<TradeInfo> OnTradePrint;

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

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

                    public override void TradePrint(TradeInfo oInfo)
                    {
                        if (OnTradePrint != null) OnTradePrint(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(); }
                }
            }
            """
            
            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(self) -> bool:
        try:
            # --- 環境変数の設定 (REngine インスタンス化前に必要) ---
            # これらは R|Trader Pro プラグインモードを有効にするために必須
            os.environ["RAPI_MD_ENCODING"] = "4"
            os.environ["RAPI_IH_ENCODING"] = "4"
            logger.info("R|API+ プラグインモード用環境変数を設定しました (Encoding=4)")

            # --- 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

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

            # --- コールバック関数の定義と登録 ---
            def _on_alert(info):
                msg = info.Message
                logger.warning(f"[R|API+] Alert: {msg}")
                
                # 接続成功/ログイン成功を検知
                msg_lower = msg.lower()
                if "login complete" in msg_lower:
                    logger.info("R|API+ ログインが正常に完了しました (READY)")
                    self._is_ready.set()
                elif "connection opened" in msg_lower:
                    logger.info(f"R|API+ 接続が開きました: {msg}")
                    # 接続だけでは不十分な場合があるため、ここでは set しない (Login Complete を待つ)
                elif "connection broken" in msg_lower or "logout" in msg_lower or "failed" in msg_lower:
                    logger.info(f"R|API+ 接続切断または失敗を検知しました: {msg}")
                    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}")

            # デリゲートへのバインド
            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)
            
            logger.info("コールバックのバインド完了")

            # REngineParamsの設定
            params = rapi.REngineParams()
            params.AppName = "RithmicBridge"
            params.AppVersion = "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}")

            # バリデーションを通過させるために実在するアドレス形式を使用する
            # (プラグインモードでは login() 時の指定が優先されるが、初期化時にこれが必要)
            base_addr = "ritpz15001.rithmic.com:56000"
            domain_name = "rithmic_paper_aws_jp_domain"

            params.DmnSrvrAddr = base_addr
            params.DomainName = domain_name
            params.LicSrvrAddr = base_addr
            params.LocBrokAddr = base_addr
            params.LoggerAddr = base_addr

            logger.info(f"REngine Params Initialized (Validation target: {base_addr})")

            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:
        if not self.engine:
            if not self.initialize():
                return False
        
        # コマンドサーバーの開始
        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
            
            # プラグインモードでの接続情報
            # R|Trader Pro は 127.0.0.1 (WindowsホストIP) で待ち受けている
            host = self.config.rithmic.host
            if host == "127.0.0.1" and "WSL_DISTRO_NAME" in os.environ:
                 # WSL2の場合はホストIPを再度確認
                 host = self._get_wsl_host() or "127.0.0.1"

            md_addr = f"{host}:3010"
            ts_addr = f"{host}:3011" 
            ih_addr = f"{host}:3012"
            
            # Env Keyは空文字でもよい
            env_key = ""

            logger.info(f"Rithmicへログイン試行 (Plugin Mode)... MD: {md_addr}, IH: {ih_addr}")
            
            self.engine.login(
                self.callbacks,
                env_key, user, password, md_addr,
                env_key, user, password, ts_addr,
                "", # pnl addr
                env_key, user, password, ih_addr
            )
            
            logger.info("ログインリクエスト送信完了。非同期結果の待機を開始します。")
            self._connected = True
            return True

        except Exception as e:
            logger.error(f"接続失敗: {e}")
            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 エラー防止)
        if not self._is_ready.is_set():
            logger.info(f"R|API+ のログイン完了 (Login Complete) を待機します (最大 {timeout}秒)...")
            if not self._is_ready.wait(timeout):
                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 publish_to_zmq(self, event: Any):
        """イベントをトピックに応じてパブリッシャーに送る"""
        from rithmic_bridge.models import MBOEvent, TradeEvent
        if isinstance(event, MBOEvent):
            self.publisher.enqueue_mbo(event.to_json())
        elif isinstance(event, TradeEvent):
            self.publisher.enqueue_trade(event.to_json())

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