""" 工具函数 """
import ctypes
import inspect
import os
import pkgutil
import random
import sys
import time
import types
import warnings
from copy import deepcopy
from datetime import datetime, time
from functools import wraps
from io import TextIOWrapper
from threading import RLock
from time import sleep
from typing import AnyStr, IO

from ctpbee.trade_time import TradingDay

_missing = object()


class locked_cached_property(object):
    def __init__(self, func, name=None, doc=None):
        self.__name__ = name or func.__name__
        self.__module__ = func.__module__
        self.__doc__ = doc or func.__doc__
        self.func = func
        self.lock = RLock()

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        with self.lock:
            value = obj.__dict__.get(self.__name__, _missing)
            if value is _missing:
                value = self.func(obj)
                obj.__dict__[self.__name__] = value
            return value


def find_package(import_name):
    root_mod_name = import_name.split('.')[0]
    loader = pkgutil.get_loader(root_mod_name)
    if loader is None or import_name == '__main__':
        package_path = os.getcwd()
    else:
        if hasattr(loader, 'get_filename'):
            filename = loader.get_filename(root_mod_name)
        elif hasattr(loader, 'archive'):
            filename = loader.archive
        else:
            __import__(import_name)
            filename = sys.modules[import_name].__file__
        package_path = os.path.abspath(os.path.dirname(filename))
        if _matching_loader_thinks_module_is_package(
                loader, root_mod_name):
            package_path = os.path.dirname(package_path)

    site_parent, site_folder = os.path.split(package_path)
    py_prefix = os.path.abspath(sys.prefix)
    if package_path.startswith(py_prefix):
        return py_prefix, package_path
    elif site_folder.lower() == 'site-packages':
        parent, folder = os.path.split(site_parent)
        # Windows like installations
        if folder.lower() == 'lib':
            base_dir = parent
        elif os.path.basename(parent).lower() == 'lib':
            base_dir = os.path.dirname(parent)
        else:
            base_dir = site_parent
        return base_dir, package_path
    return None, package_path


def _matching_loader_thinks_module_is_package(loader, mod_name):
    if hasattr(loader, 'is_package'):
        return loader.is_package(mod_name)
    # importlib's namespace loaders do not have this functionality but
    # all the modules it loads are packages, so we can take advantage of
    # this information.
    elif (loader.__class__.__module__ == '_frozen_importlib' and
          loader.__class__.__name__ == 'NamespaceLoader'):
        return True
    # Otherwise we need to fail with an error that explains what went
    # wrong.
    raise AttributeError(
        ('%s.is_package() method is missing but is required by CtpBee of '
         'PEP 302 import hooks.  If you do not use import hooks and '
         'you encounter this error please file a bug against Flask.') %
        loader.__class__.__name__)


def check(type: AnyStr):
    """ 检查接口是否存在 """

    def midlle(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            if type == "market":
                if args[0].app.market is None:
                    raise ValueError("当前账户行情api未连接,请检查你的代码中是否使用了行情接口API")
            elif type == "trader":
                if args[0].app.market is None:
                    raise ValueError("当前账户交易api未连接,请检查你的代码中是否使用了交易接口API")
            else:
                raise ValueError("非法字符串")
            return func(*args, **kwargs)

        return wrapper

    return midlle


def graphic_pattern(version, engine_method):
    first = f"""                                                            
               @             @                           
                )           (                                 
            #####################                               
          ##                     ##                            
         ##                       ##                                                  
        ##   ctpbee   :{version.ljust(12, ' ')}##                          
        ##                         ##                          
        ##   engine   :{engine_method.ljust(12, ' ')}##                          
         ##                       ##                          
          ++++++++    +    ++++++++                      
       (|||||||||| + +++ + ||||||||||)                          
          +++++++ +++++++++ +++++++                            
                   +++++++
                      T                                        
        """

    second = f"""             
         @@@                     @@@                             
            @@                 @@                              
              @               @                                 
          +#######################+                            
         ##                       ##                                                  
         ##  ctpbee:    {version.ljust(10, ' ')}##                          
         ##                       ##                          
         ##  engine:    {engine_method.ljust(10, ' ')}##                          
         ##                       ##                          
          ++++++++    +    ++++++++                      
       (|||||||||| + +++ + ||||||||||)                          
          +++++++ +++++++++ +++++++                            
                   +++++++
                      T                                        
        """
    three = f"""
    {"*" * 60}                                                               
    *                                                          *
    *          -------------------------------------           *
    *          |                                   |           *
    *          |      ctpbee:    {version.ljust(16, " ")}  |           *
    *          |                                   |           *
    *          |      engine:    {engine_method.ljust(16, " ")}  |           *
    *          |                                   |           *
    *          -------------------------------------           *
    *                                                          *
    {"*" * 60}                       
             """

    return random.choice([first, second, three])


def dynamic_loading_api(f):
    """
    f 是文件流
    主要是用来通过文件动态载入策略。 返回策略类的实例, 应该通过Ctpbee.add_extension() 加以载入
    你需要在策略代码文件中显式指定ext
    返回
    """
    if not isinstance(f, IO) and not isinstance(f, TextIOWrapper):
        raise ValueError(f"请确保你传入的是文件流(IO),而不是{str(type(f))}")
    d = types.ModuleType("object")
    d.__file__ = f.name
    exec(compile(f.read(), f.name, 'exec'), d.__dict__)
    if not hasattr(d, "ext"):
        raise AttributeError("请检查你的策略中是否包含ext变量")
    return d.ext


def auth_check_time(timed: datetime):
    """ 检查启动时间 """
    data_time = timed.time()
    if not isinstance(data_time, time):
        raise TypeError("参数类型错误, 期望为datatime.time}")
    DAY_START = time(8, 45)  # 日盘启动和停止时间
    DAY_END = time(15, 5)
    NIGHT_START = time(20, 45)  # 夜盘启动和停止时间
    NIGHT_END = time(2, 35)
    if data_time <= DAY_END and data_time >= DAY_START:
        return True
    if data_time >= NIGHT_START:
        return True
    if data_time <= NIGHT_END:
        return True
    return False


def run_forever(app):
    """
        永久运行函数 60秒检查一次
        非交易日不进行载入

    """

    running_me = False
    running_status = True
    while True:
        if not app.p_flag:
            break
        current_time = datetime.now()
        if TradingDay.is_holiday(current_time) or TradingDay.is_weekend(current_time):
            running_me = False
        else:
            running_me = True
        if auth_check_time(current_time):
            pass
        else:
            running_me = False
        if running_me and not running_status:
            """ 到了该启动的时间但是没运行 """
            app.recorder.clear_all()  # 清空记录器中所有的数据
            app.reload()  # 重载接口
            for x in app.extensions.keys():
                app.enable_extension(x)
            print(f"重新进行自动登录, 时间: {str(current_time)}")
            running_status = True

        elif running_me and running_status:
            """ 到了该启动的时间已经在运行 """

        elif not running_me and running_status:
            """ 非交易日 并且在运行 """
            for x in app.extensions.keys():
                app.suspend_extension(x)
                if hasattr(app.extensions[x], "f_init"):
                    app.extensions[x].f_init = False
            print(f"当前时间不允许, 时间: {str(current_time)}, 即将阻断运行")
            running_status = False

        elif not running_me and not running_status:
            """  非交易日 并且不运行 """

        sleep(1)


def refresh_query(app):
    """ 循环查询 """
    while True:
        sleep(1)
        cur = datetime.now()
        if not TradingDay.is_trading_day(cur) or not auth_check_time(cur):
            continue
        app.query_position()
        sleep(app.config['REFRESH_INTERVAL'])
        app.query_account()
        if not app.r_flag:
            break


def value_call(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        d = func(*args, **kwargs)
        self, event = args
        for value in self.app.extensions.values():
            if self.app.config['INSTRUMENT_INDEPEND']:
                if len(value.instrument_set) == 0:
                    warnings.warn("你当前开启策略对应订阅行情功能, 当前策略的订阅行情数量为0,请确保你的订阅变量是否为instrument_set,以及订阅具体代码")
                if event.data.local_symbol in value.instrument_set:
                    value(deepcopy(event))
            else:
                value(deepcopy(event))
        return d

    return wrapper


def async_value_call(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        d = await func(*args, **kwargs)
        self, event = args
        for value in self.app.extensions.values():
            if self.app.config['INSTRUMENT_INDEPEND']:
                if len(value.instrument_set) == 0:
                    warnings.warn("你当前开启策略对应订阅行情功能, 当前策略的订阅行情数量为0,请确保你的订阅变量是否为instrument_set,以及订阅具体代码")
                if event.data.local_symbol in value.instrument_set:
                    await value(deepcopy(event))
            else:
                await value(deepcopy(event))
        return d

    return wrapper


def _async_raise(tid, exctype):
    """raises the exception, performs cleanup if needed"""
    tid = ctypes.c_long(tid)
    if not inspect.isclass(exctype):
        exctype = type(exctype)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")


def end_thread(thread):
    """
    which used to kill thread !
    """
    _async_raise(thread.ident, SystemExit)


def exec_wrapper(func):
    """ 错误装饰器 """


def exec_intercept(self, func):
    """
    此函数主要用于CtpbeeApi的Action结果拦截,保证用户简单调用,实现暗地结果处理

    """

    @wraps(func)
    def wrapper(*args, **kwargs):
        if func:
            result = func(*args, **kwargs)
            self.api.resolve_callback(func.__name__, result)
            return result
        else:
            return None

    return wrapper