"""
OnionMeta: A Python metaclass for dynamic abstract method resolution
Implements the onion architecture pattern, decoupling core business logic from implementations

线程安全：使用 WeakKeyDictionary 确保每个线程有独立的实现存储，线程结束时自动清理

## Onion 实现类编写规范

实现类（继承自 Onion 基类的具体实现）必须遵守以下约定，违规将抛出 `OnionViolationError`：

### 1. 禁止定义 `__init__`
所有初始化逻辑应在基类中统一处理。子类不允许定义 `__init__` 方法。

```python
# ❌ 错误
class MyImpl(Calculator):
    def __init__(self, config):
        self.config = config

# ✅ 正确
class MyImpl(Calculator):
    pass  # 基类负责初始化
```

### 2. 禁止定义新方法
只能实现基类声明的抽象方法，不允许添加基类中不存在的新方法。

```python
# ❌ 错误
class MyImpl(Calculator):
    def extra_helper(self):  # 新方法！
        pass

# ✅ 正确
class MyImpl(Calculator):
    def add(self, a, b):  # 实现基类抽象方法
        return a + b
```

### 3. 禁止类属性
配置通过基类初始化参数传入，子类不允许定义类属性。

```python
# ❌ 错误
class MyImpl(Calculator):
    PRECISION = 2  # 类属性！

# ✅ 正确
# 配置通过基类 __init__ 参数传入
```

### 4. 辅助函数
需要辅助逻辑时，使用模块级私有函数，而非方法。

```python
# ✅ 正确
def _helper_function(x):
    return x * 2

class MyImpl(Calculator):
    def add(self, a, b):
        return _helper_function(a + b)
```

### 异常说明
- `OnionViolationError`: 违反上述约定时抛出
"""

import abc
import sys
import threading
import weakref
import warnings
from typing import Dict, List, Type, Any, Optional

# 标记：用于表示属性在卸载时应被删除
_DELETE_MARKER = object()

# 线程存储 - 使用 WeakKeyDictionary，线程结束时自动清理
_thread_storage = weakref.WeakKeyDictionary()
_storage_lock = threading.Lock()


def _get_current_thread():
    """获取当前线程对象"""
    return threading.current_thread()


def _check_asyncio_compatibility():
    """
    防御性检查：拒绝在协程中运行

    协程共享线程，但 Onion 架构以线程为键存储状态，
    会导致协程间状态污染。
    """
    try:
        import asyncio
        # 检查是否有正在运行的事件循环
        try:
            loop = asyncio.get_running_loop()
            # 有事件循环，检查是否在协程中
            if asyncio.current_task(loop) is not None:
                raise RuntimeError(
                    f"Onion architecture does not support usage inside asyncio coroutines "
                    f"(thread: {threading.current_thread().name}). "
                    f"Please use in synchronous code, or isolate with threading.Thread."
                )
        except RuntimeError:
            # 没有运行的事件循环，正常继续
            pass
    except ImportError:
        # asyncio 不可用，正常继续
        pass


def _get_thread_storage(cls_id):
    """获取当前线程的存储空间"""
    thread = _get_current_thread()
    with _storage_lock:
        storage = _thread_storage.get(thread)
        if storage is None:
            return None
        return storage.get(cls_id)


def _init_thread_storage(cls_id):
    """初始化当前线程的存储空间，注册线程结束时的清理动作"""
    # 防御性检查：拒绝在协程中运行
    _check_asyncio_compatibility()

    thread = _get_current_thread()
    with _storage_lock:
        if thread not in _thread_storage:
            # 注册线程结束时的清理动作
            weakref.finalize(thread, _cleanup_thread_storage, thread)
            _thread_storage[thread] = {}
        if cls_id not in _thread_storage[thread]:
            _thread_storage[thread][cls_id] = {
                '_onion_subs': [],
                '_onion_built': False,
                '_onion_merged': {},
                '_onion_orig_abstractmethods': None
            }
        return _thread_storage[thread][cls_id]


def _check_thread_safety(cls, method_name):
    """检查线程安全 - 拒绝跨线程访问"""
    storage = _get_thread_storage(id(cls))
    if storage is None:
        raise RuntimeError(
            f"Cross-thread access detected in '{method_name}': "
            f"Class '{cls.__name__}' implementation is not initialized in current thread. "
            f"Each thread must load its own implementations."
        )


def _cleanup_thread_storage(thread):
    """清理指定线程的存储（线程结束时自动调用）"""
    with _storage_lock:
        if thread in _thread_storage:
            del _thread_storage[thread]


class OnionViolationError(TypeError):
    """
    违反 Onion 架构约定的异常

    当子类违反以下约定时抛出：
    - 定义了新的类属性或实例属性
    - 定义了 __init__ 方法
    - 添加了基类中不存在的新方法
    """
    pass


class OnionMeta(abc.ABCMeta):
    """
    Onion Architecture Metaclass

    This metaclass implements the "Construction & Binding" (C&B) process, dynamically
    collecting and integrating methods from all implementation submodules at first instantiation.
    """

    # Internal methods: private methods starting with _
    # Note: impls loading is user responsibility, no auto-loading provided

    def _get_thread_storage(cls):
        """获取当前线程的存储空间"""
        return _get_thread_storage(id(cls))

    def _init_thread_storage(cls):
        """初始化当前线程的存储空间"""
        return _init_thread_storage(id(cls))

    def _check_thread_safety(cls, method_name):
        """检查线程安全"""
        _check_thread_safety(cls, method_name)

    def _get_onion_subs(cls) -> List[Type]:
        """Retrieve all registered non-abstract subclasses (thread-local)"""
        storage = cls._get_thread_storage()
        if storage is None:
            return []
        subs = list(storage.get('_onion_subs', []))
        # [DEBUG] found {len(subs)} subclasses
        return subs

    def _get_all_subs(cls) -> List[Type]:
        """
        递归收集 cls 及其所有 OnionMeta 基类的 _onion_subs (thread-local)
        返回一个 去重+保持顺序 的列表
        """
        seen = set()
        subs = []
        # 按 MRO 顺序，保证离 cls 越近的优先
        for base in cls.__mro__:
            if isinstance(base, OnionMeta):
                storage = base._get_thread_storage()
                if storage:
                    for sub in storage.get('_onion_subs', []):
                        if sub not in seen:
                            seen.add(sub)
                            subs.append(sub)
        return subs

    def _get_base_attrs_and_methods(cls) -> tuple[set, set]:
        """
        获取基类中定义的所有属性和方法名称（不包括抽象方法）

        Returns:
            (base_attrs, base_methods): 基类属性名集合和基类方法名集合
        """
        base_attrs = set()
        base_methods = set()

        # 遍历 MRO 中所有 OnionMeta 基类（除了当前类）
        for base in cls.__mro__:
            if base is cls:
                continue
            if isinstance(base, OnionMeta):
                # 收集基类中定义的属性
                for attr_name in base.__dict__:
                    if attr_name.startswith('_'):
                        continue
                    attr_val = base.__dict__[attr_name]
                    if callable(attr_val) or isinstance(attr_val, property):
                        base_methods.add(attr_name)
                    else:
                        base_attrs.add(attr_name)

        return base_attrs, base_methods

    def _is_first_onion_subclass(cls, sub: Type) -> bool:
        """
        判断一个类是否是第一个继承自 Onion 的类（中间基类）

        第一个 Onion 子类的特征：
        - 它的直接基类中包含 Onion（根基类）
        - 但不是继承自其他使用了 OnionMeta 的中间基类

        Args:
            sub: 要检查的类

        Returns:
            True 如果是第一个 Onion 子类（应该跳过检查），False 否则
        """
        # 检查是否直接继承自 Onion（根基类）
        for base in sub.__bases__:
            if base.__name__ == 'Onion':
                return True
        return False

    def _get_all_base_fields(cls, sub: Type) -> set:
        """
        获取基类中定义的所有字段名（包括私有字段、方法、属性等）

        Args:
            sub: 子类

        Returns:
            基类字段名集合
        """
        base_fields = set()

        # 遍历 MRO 中所有 OnionMeta 基类（除了当前类自身）
        for base in sub.__mro__:
            if base is sub:
                continue
            if isinstance(base, OnionMeta):
                # 收集基类中定义的所有字段
                for attr_name in base.__dict__:
                    base_fields.add(attr_name)

        return base_fields

    def _validate_subclass(cls, sub: Type, base_attrs: set, base_methods: set,
                           base_abstract_methods: set) -> List[str]:
        """
        验证子类是否违反了 Onion 架构约定

        跳过对第一个继承自 Onion 的类的检查（如 AICore），
        只检查更深层次的实现类（如 KimiAI）

        Args:
            sub: 子类
            base_attrs: 基类属性名集合
            base_methods: 基类方法名集合
            base_abstract_methods: 基类抽象方法名集合

        Returns:
            违规信息列表，空列表表示没有违规
        """
        violations = []

        # 跳过第一个继承自 Onion 的类（中间基类），只检查实现类
        if cls._is_first_onion_subclass(sub):
            return violations

        # 获取基类中定义的所有字段（包括私有字段）
        base_all_fields = cls._get_all_base_fields(sub)

        # 检查子类 __dict__ 中定义的所有内容
        for attr_name, attr_val in sub.__dict__.items():
            # 跳过 Python 内部特殊属性（__module__, __qualname__, __doc__ 等）
            if attr_name.startswith('__') and attr_name.endswith('__'):
                if attr_name == '__init__':
                    violations.append(
                        f"子类 {sub.__name__} 定义了 __init__ 方法，违反约定："
                        "子类不允许定义 __init__，所有初始化逻辑应在基类中统一处理"
                    )
                continue

            # 跳过 ABC 相关的内部属性
            if attr_name.startswith('_abc'):
                continue

            # 检查是否是基类中已定义的字段
            if attr_name not in base_all_fields:
                # 这是一个基类中没有的新字段
                if callable(attr_val) and not isinstance(attr_val, property):
                    violations.append(
                        f"子类 {sub.__name__} 定义了新方法 '{attr_name}'，违反约定："
                        "子类只能实现基类中定义的抽象方法，不允许添加新方法"
                    )
                elif isinstance(attr_val, property):
                    violations.append(
                        f"子类 {sub.__name__} 定义了新属性 '{attr_name}'，违反约定："
                        "子类不允许定义新属性，需要新属性时应向基类申请"
                    )
                elif attr_name.startswith('_'):
                    # 私有字段（单下划线开头或双下划线开头但不在上面处理）
                    violations.append(
                        f"子类 {sub.__name__} 定义了私有字段 '{attr_name}'，违反约定："
                        "子类不允许定义基类中不存在的任何字段"
                    )
                else:
                    # 其他类属性
                    violations.append(
                        f"子类 {sub.__name__} 定义了新类属性 '{attr_name}'，违反约定："
                        "子类不允许定义类属性，需要新属性时应向基类申请"
                    )
            else:
                # 字段在基类中存在，检查是否是覆盖非抽象方法
                if attr_name in base_methods and attr_name not in base_abstract_methods:
                    # 这是一个基类中已定义的非抽象方法/属性，禁止覆盖
                    if callable(attr_val) or isinstance(attr_val, property):
                        violations.append(
                            f"子类 {sub.__name__} 覆盖了基类的非抽象方法/属性 '{attr_name}'，违反约定："
                            "子类只能实现抽象方法，不允许覆盖基类的非抽象方法"
                        )

        return violations

    def _get_meths(cls, subs: List[Type], warns: List[str]) -> Dict[str, Any]:
        """Collect method implementations from subclasses (with cross-subclass resolution)"""
        meths = {}
        srcs = {}  # Track source of each method
        attrs = {}  # Track class attributes that need to be copied

        # 获取基类定义的属性和方法
        base_attrs, base_methods = cls._get_base_attrs_and_methods()
        base_abstract_methods = cls.__abstractmethods__

        # 收集所有违规信息
        all_violations = []

        # 首先，为每个抽象方法查找具体实现（跨所有子类及其 MRO）
        for meth_name in cls.__abstractmethods__:
            # 在所有子类中查找第一个具体实现
            for sub in subs:
                # 检查子类本身及其 MRO
                for klass in [sub] + list(sub.__mro__):
                    if klass is cls:
                        continue
                    if meth_name in klass.__dict__:
                        meth = klass.__dict__[meth_name]
                        # Check if it's concretely implemented (not abstract)
                        is_abstract = getattr(meth, '__isabstractmethod__', False)
                        if not is_abstract:
                            if meth_name in meths:
                                warns.append(
                                    f"Method conflict: {meth_name} implemented in both {klass.__name__} "
                                    f"and {srcs[meth_name]}, using {klass.__name__}'s implementation"
                                )
                            else:
                                srcs[meth_name] = klass.__name__
                            meths[meth_name] = meth
                        break  # 找到第一个实现就停止
                if meth_name in meths:
                    break  # 已找到实现，检查下一个方法

        # 然后验证子类约定
        for sub in subs:
            # 对于每个子类，获取其所有基类（包括中间基类如 AICore）的字段
            sub_base_all_fields = cls._get_all_base_fields(sub)
            sub_base_methods = set()
            for base in sub.__mro__:
                if base is sub:
                    continue
                if isinstance(base, OnionMeta):
                    for attr_name, attr_val in base.__dict__.items():
                        if attr_name.startswith('__') and attr_name.endswith('__'):
                            continue
                        if callable(attr_val) or isinstance(attr_val, property):
                            sub_base_methods.add(attr_name)

            violations = cls._validate_subclass(
                sub, sub_base_all_fields, sub_base_methods, base_abstract_methods
            )
            all_violations.extend(violations)

            # Collect class attributes that are used by the implementation
            # Only collect attributes that are defined in the subclass (not inherited)
            for attr_name in sub.__dict__:
                attr_val = sub.__dict__[attr_name]
                # Skip special attributes and methods
                if attr_name.startswith('__') and attr_name.endswith('__'):
                    continue
                if callable(attr_val) or isinstance(attr_val, property):
                    continue
                # Skip ABC-related attributes
                if attr_name.startswith('_abc'):
                    continue

                if attr_name in attrs and attrs[attr_name] != attr_val:
                    warns.append(
                        f"Attribute conflict: {attr_name} defined differently in {sub.__name__} "
                        f"and previous implementation, using {sub.__name__}'s value"
                    )
                attrs[attr_name] = attr_val

        # 如果有违规，抛出特殊异常
        if all_violations:
            err_msg = f"Onion 架构约定违规:\n" + "\n".join(f"- {v}" for v in all_violations)
            raise OnionViolationError(err_msg)

        #DEBUG collected {len(meths)} methods and {len(attrs)} attributes from {len(subs)} subclasses
        return meths, attrs

    def _merge_meths(cls, meths: Dict[str, Any], attrs: Dict[str, Any], warns: List[str]):
        """Merge method implementations and attributes into core class (thread-local)"""
        storage = cls._init_thread_storage()

        # Merge methods and properties
        for meth_name, meth in meths.items():
            # 记录原始值（如果是新属性，记录为 DELETE_MARKER）
            if meth_name not in storage['_onion_merged']:
                if hasattr(cls, meth_name):
                    storage['_onion_merged'][meth_name] = getattr(cls, meth_name)
                else:
                    storage['_onion_merged'][meth_name] = _DELETE_MARKER
            setattr(cls, meth_name, meth)
            #DEBUG merged method: {meth_name}

        # Merge class attributes
        for attr_name, attr_val in attrs.items():
            if attr_name not in storage['_onion_merged']:
                if hasattr(cls, attr_name):
                    storage['_onion_merged'][attr_name] = getattr(cls, attr_name)
                else:
                    storage['_onion_merged'][attr_name] = _DELETE_MARKER
            setattr(cls, attr_name, attr_val)
            #DEBUG merged attribute: {attr_name} = {attr_val}

    def _handle_warns(cls, warns: List[str]):
        """Handle warning messages"""
        for warn in warns:
            warnings.warn(warn, RuntimeWarning)
            # [DEBUG] warning: {warn}

    def _raise_errs(cls, errs: List[str]):
        """Handle compilation errors"""
        err_msg = f"Failed to compile class '{cls.__name__}':\n" + "\n".join(f"- {err}" for err in errs)
        # [DEBUG] compile errors: {err_msg}
        raise TypeError(err_msg)

    def _compile(cls):
        """
        Construction & Binding (C&B) 核心流程 (thread-local)
        升级点：通过 _get_all_subs 把兄弟核心类的实现也拉进来
        """
        errs, warns = [], []

        # 0. 保存原始的抽象方法（用于卸载时恢复）
        storage = cls._init_thread_storage()
        if storage['_onion_orig_abstractmethods'] is None:
            storage['_onion_orig_abstractmethods'] = frozenset(cls.__abstractmethods__)

        # 1. 收集所有相关实现子类（新增）
        subs = cls._get_all_subs()
        # [DEBUG] compiling {cls.__name__}, collected subs: {[s.__name__ for s in subs]}

        if not subs:
            errs.append(f"No implementation subclasses found for {cls.__name__}")
            cls._raise_errs(errs)
            return

        # 2. 收集方法与属性（复用原有逻辑）
        try:
            meths, attrs = cls._get_meths(subs, warns)
        except OnionViolationError:
            # 重新抛出，保留原始异常
            raise

        # 3. 检查是否还有未实现的抽象方法
        miss_meths = cls.__abstractmethods__ - set(meths.keys())
        if miss_meths:
            errs.append(f"Missing implementations for abstract methods: {miss_meths}")
            cls._raise_errs(errs)
            return

        # 4. 合并到核心类（复用原有逻辑）
        cls._merge_meths(meths, attrs, warns)

        # 5. 发出警告（复用原有逻辑）
        if warns:
            cls._handle_warns(warns)

    # Public API: methods for external use
    def onionCompile(cls):
        """
        Manually trigger Construction & Binding (C&B) process (thread-local)

        Should be called after user loads impls modules, or rely on auto-trigger at first instantiation

        :param: None

        :raises: TypeError: If compilation fails (missing implementation subclasses or abstract methods)
        :raises: OnionViolationError: If subclass violates architecture conventions
        :warns: RuntimeWarning: For warning situations like method conflicts

        :usage:
            # User responsible for loading impls
            import impls
            # Manually trigger compilation
            MyCoreClass.onionCompile()
        """
        storage = cls._init_thread_storage()
        if not storage['_onion_built']:
            cls._compile()
            storage['_onion_built'] = True

            # Recalculate abstract methods
            if hasattr(abc, 'update_abstractmethods'):
                abc.update_abstractmethods(cls)
                # [DEBUG] manually updated abstractmethods for {cls.__name__}
        else:
            # [DEBUG] {cls.__name__} already compiled, skipping
            pass

    def OnionUnload(cls, *modules: str):
        """
        完全卸载当前实现，重置类到未编译状态，并清理指定模块 (thread-local)

        这是切换实现的唯一方式。调用后必须通过重新 import 加载新实现。
        注意：每个线程必须独立加载自己的实现，禁止跨线程共享实现。

        :param modules: 要清理的模块路径列表，如 'ai.kimi_ai', 'ai.deepseek_api'
                       支持通配符前缀，如 'ai.kimi' 会清理所有 'ai.kimi*' 开头的模块
                       如果不指定，则自动卸载所有已注册实现类所在的模块
        :type modules: str

        :usage:
            # 切换到 DeepSeek 实现（指定模块）
            AICore.OnionUnload('ai.kimi_ai', 'ai.kimi_config')
            from ai import deepseek_ai  # 自动注册 DeepSeekAI 实现
            ai = AICore()  # 使用新实现

            # 切回 Kimi（指定模块）
            AICore.OnionUnload('ai.deepseek_ai', 'ai.deepseek_config')
            from ai import kimi_ai  # 重新 import，重新注册
            ai = AICore()

            # 卸载所有实现（不指定模块）
            AICore.OnionUnload()  # 自动清理所有已注册实现类所在的模块
            from ai import kimi_ai  # 重新 import，重新注册
            ai = AICore()
        """
        # 获取线程存储
        storage = cls._get_thread_storage()
        if storage is None:
            # 当前线程没有初始化过，无需卸载
            storage = cls._init_thread_storage()

        # 1. 清理 C&B 合并的方法和属性（如果已编译）
        for attr_name, original_val in storage['_onion_merged'].items():
            if original_val is _DELETE_MARKER:
                if hasattr(cls, attr_name):
                    try:
                        delattr(cls, attr_name)
                    except (AttributeError, TypeError):
                        pass
            else:
                try:
                    setattr(cls, attr_name, original_val)
                except (AttributeError, TypeError):
                    pass
        storage['_onion_merged'] = {}

        # 2. 重置编译标志
        storage['_onion_built'] = False

        # 3. 恢复抽象方法
        if storage['_onion_orig_abstractmethods'] is not None:
            cls.__abstractmethods__ = storage['_onion_orig_abstractmethods']

        # 4. 清理指定模块（去加载）
        # 如果没有指定模块，自动收集所有已注册实现类所在的模块
        # 注意：必须在清空子类注册表之前收集模块信息！
        if not modules:
            subs = cls._get_all_subs()
            # 排除核心类自身的模块（避免卸载 Calculator 类本身所在的模块）
            core_module = cls.__module__
            modules = tuple(set(
                sub.__module__ for sub in subs
                if sub.__module__ != core_module
            ))

        # 5. 完全清空子类注册表（当前类及所有基类）
        for base in cls.__mro__:
            if isinstance(base, OnionMeta):
                base_storage = base._get_thread_storage()
                if base_storage:
                    base_storage['_onion_subs'] = []

        modules_to_remove = set()

        for pattern in modules:
            if '*' in pattern:
                # 通配符匹配
                import fnmatch
                for mod_name in list(sys.modules.keys()):
                    if fnmatch.fnmatch(mod_name, pattern):
                        modules_to_remove.add(mod_name)
            else:
                # 精确匹配 + 子模块匹配
                # 例如 'ai.kimi' 会匹配 'ai.kimi', 'ai.kimi.utils' 等
                for mod_name in list(sys.modules.keys()):
                    if mod_name == pattern or mod_name.startswith(pattern + '.'):
                        modules_to_remove.add(mod_name)

        # 按依赖顺序删除（先删除子模块，再删除父模块）
        # 使用 import 锁确保线程安全
        import _imp
        _imp.acquire_lock()
        try:
            for mod_name in sorted(modules_to_remove, key=lambda x: -x.count('.')):
                if mod_name in sys.modules:
                    del sys.modules[mod_name]
        finally:
            _imp.release_lock()

    # Magic methods section
    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace, **kwargs)

        # 确保当前类初始化线程存储
        cls._init_thread_storage()

        # 线程本地存储初始化 - 注册子类到基类
        for base in bases:
            if isinstance(base, OnionMeta):
                # Register all subclasses, including partial implementations
                if cls.__name__ != base.__name__:  # Avoid self-registration
                    base_storage = base._init_thread_storage()
                    base_storage['_onion_subs'].append(cls)
                    #DEBUG registered subclass: {cls.__name__} to {base.__name__}

    def __call__(cls, *args, **kwargs):
        """
        Intercept first instantiation to trigger C&B process (thread-local)

        Uses _onion_built flag to ensure C&B runs only once per thread
        """
        storage = cls._init_thread_storage()
        if not storage['_onion_built']:
            cls._compile()
            storage['_onion_built'] = True

            # Recalculate abstract methods
            if hasattr(abc, 'update_abstractmethods'):
                abc.update_abstractmethods(cls)
                # [DEBUG] updated abstractmethods for {cls.__name__}

        return super().__call__(*args, **kwargs)
