"""Python version of `demo/xlt_quote_api_demo`.

This module mirrors the C++ demo structure:
- `XLTQuoteDemo` class wraps initialize/login/subscribe/query calls.
- SPI callbacks live in `quote_spi.py`.

Note on `subscribe_quote`:
- The C++ API expects `quote_thread_t*` (array).
- The SWIG binding represents `quote_thread_t.mc_types` as a fixed-size C array
  (`QuoteMCType[64]`). To fill it from Python we use the SWIG-generated helper
  `QuoteMCTypeArray64` and pass `arr.cast()` into the mc_types setter.

"""

from __future__ import annotations

import threading
import time
from dataclasses import dataclass

import py_xlt_api as xlt

try:
    # Running as a script from this folder
    import config as _demo_config
    from quote_spi import _QuoteSpi
except ImportError:  # pragma: no cover
    # Running as a package/module
    from . import config as _demo_config
    from .quote_spi import _QuoteSpi

# Public re-export (like other demos)
default_config = _demo_config.default_config

__all__ = [
    "QuoteAccount",
    "XLTQuoteDemo",
    "default_config",
]



@dataclass
class QuoteAccount:
    client_id: int
    account_name: str
    password: str
    pub_key_base64: str = "XXXXXXXXXXXXXX"


class XLTQuoteDemo:
    def __init__(self, config: xlt.XLTQuoteConfig | None = None):
        self._api: xlt.XLTQuoteApi = xlt.XLTQuoteApi.create()
        self._spi = _QuoteSpi(self)
        self._config = config

        self._session_id = 0
        self._login_success = False
        self._login_finished = False
        self._request_id = 0
        self._login_event = threading.Event()


    # ---- C++-like accessors ----
    def api_version(self) -> str:
        # XLTQuoteApi.get_api_version() is static in C++, but SWIG exposes it as instance method too.
        try:
            return self._api.get_api_version()
        except TypeError:
            return xlt.XLTQuoteApi.get_api_version()

    def trading_day(self) -> int:
        # # Some C++ implementations may segfault if called before initialize/login.
        # if hasattr(self._api, "get_trading_day_safe"):
        #     return int(self._api.get_trading_day_safe())
        return int(self._api.get_trading_day())

    def next_request_id(self) -> int:
        self._request_id += 1
        return self._request_id

    @property
    def session_id(self) -> int:
        return int(self._session_id)

    def initialize(self) -> bool:
        if self._config is None:
            raise RuntimeError("XLTQuoteDemo requires a configured XLTQuoteConfig")

        ok = self._api.initialize(self._config, self._spi)
        if not ok:
            err = self._api.get_last_error()
            print("initialize failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False
        print("initialize success")
        return True

    def login(self, account: QuoteAccount) -> bool:
        if self._session_id != 0:
            self._api.logout(self._session_id)

        self._session_id = 0
        self._login_success = False
        self._login_finished = False
        self._login_event.clear()

        req_id = self.next_request_id()
        ok = self._api.login(req_id, int(account.client_id), account.account_name, account.password, account.pub_key_base64)
        if not ok:
            err = self._api.get_last_error()
            print("login failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False

        self._login_event.wait(timeout=10)
        return bool(self._login_success and self._session_id != 0)

    def subscribe_quote(self) -> bool:
        # 行情接收线程配置（与C++ demo对齐）
        plan = [
            # 行情接收线程一，绑定到CPU0，订阅XLIGHT深交所L1行情
            (0, [xlt.XLT_SZSE_L1]),
            # # 行情接收线程二，绑定到CPU1，订阅盛立上交所level2行情
            # (1, [xlt.HPF_SSE_L2_STOCK_SNAP, xlt.HPF_SSE_L2_STOCK_TICK, xlt.HPF_SSE_L2_INDEX, xlt.HPF_SSE_L2_BOND_SNAP, xlt.HPF_SSE_L2_BOND_TICK]),
            # # 行情接收线程三，绑定到CPU2，订阅盛立深交所level2行情
            # (2, [xlt.HPF_SZSE_L2_STOCK_SNAP, xlt.HPF_SZSE_L2_STOCK_TICK, xlt.HPF_SZSE_L2_INDEX, xlt.HPF_SZSE_L2_BOND_SNAP, xlt.HPF_SZSE_L2_BOND_TICK]),
        ]

        ok = self._api.subscribe_quote_plan(self._session_id, plan, int(xlt.RX_MODE_AUTO))

        if not ok:
            err = self._api.get_last_error()
            print("subscribe_quote failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False

        print("subscribe_quote request send success")
        return True

    def query_static_quote_info(self) -> bool:
        ok = self._api.query_static_quote_info(self._session_id, self.next_request_id())
        if not ok:
            err = self._api.get_last_error()
            print("query_static_quote_info failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False
        print("query_static_quote_info request send success")
        return True

    def request_rebuild_tick_quote(self, exchange_index: int, channel_no: int,
                                   begin_sequence: int, end_sequence: int) -> bool:
        req = xlt.xlt_rebuild_tick_quote_req_t()
        req.exchange_index = int(exchange_index)
        req.channel_no = int(channel_no)
        req.begin_sequence = int(begin_sequence)
        req.end_sequence = int(end_sequence)

        ok = self._api.request_rebuild_tick_quote(self._session_id, self.next_request_id(), req)
        if not ok:
            err = self._api.get_last_error()
            print("request_rebuild_tick_quote failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False
        print("request_rebuild_tick_quote success: { exchange_index = %d, channel_no = %d, begin_sequence = %d, end_sequence = %d }"
              % (int(exchange_index), int(channel_no), int(begin_sequence), int(end_sequence)))
        return True

    def query_level1_quote(self, exchange_index: int, security_code: str) -> bool:
        """查询单个证券level1行情（对应C++ demo query_level1_quote）。"""
        ok = self._api.query_level1_quote(self._session_id, self.next_request_id(),
                                          int(exchange_index), security_code)
        if not ok:
            err = self._api.get_last_error()
            print("query_level1_quote failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False
        print("query_level1_quote request send success: { exchange_index = %d, security_code = %s }"
              % (int(exchange_index), security_code))
        return True

    def query_all_level1_quote(self) -> bool:
        """查询全量level1行情（对应C++ demo query_all_level1_quote）。"""
        ok = self._api.query_all_level1_quote(self._session_id, self.next_request_id())
        if not ok:
            err = self._api.get_last_error()
            print("query_all_level1_quote failed: { error_code_type = %d, error_code = %d, error_msg = %s }"
                  % (int(err.error_code_type), int(err.error_code), err.error_msg))
            return False
        print("query_all_level1_quote request send success")
        return True

    def shutdown(self):
        """Gracefully stop the API so C++ background threads exit
        *before* the Python interpreter starts finalizing.

        Call order: set _shutting_down → unsubscribe_quote → logout → os._exit.

        Why ``os._exit``?
        -----------------
        Even after ``unsubscribe_quote`` returns, a C++ callback may
        *already* be executing Python code (past the ``_shutting_down``
        guard).  If we let the interpreter finalize normally,
        ``take_gil`` inside CPython 3.13 calls ``PyThread_exit_thread``
        which triggers ``_Unwind_ForcedUnwind`` through C++ frames and
        ultimately ``abort()``.  ``os._exit(0)`` terminates the process
        immediately – no atexit handlers, no GIL finalization, no crash.
        """
        import os, sys

        # Tell the SPI to ignore any further callbacks immediately,
        # reducing the race window during unsubscribe/logout.
        self._spi._shutting_down = True

        if self._session_id != 0:
            print("shutdown: unsubscribe_quote ...")
            self._api.unsubscribe_quote(self._session_id)
            # Give in-flight callbacks time to return to C++
            time.sleep(1)
            print("shutdown: logout ...")
            self._api.logout(self._session_id)
            self._session_id = 0
            time.sleep(0.5)

        print("shutdown: done")
        # Flush before hard-exit so all output is visible.
        sys.stdout.flush()
        sys.stderr.flush()
        os._exit(0)

    def run_forever(self):
        try:
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            print("\nCtrl+C received, shutting down ...")
            self.shutdown()



