"""
Onion - Base class for onion architecture

Provides usage similar to abc.ABC, users only need to inherit Onion to use the onion architecture pattern.

==============================================================================
Onion 实现类编写规范 (重要!)
==============================================================================

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

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

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

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

4. **辅助函数**
   需要辅助逻辑时，使用模块级私有函数（`def _helper():`），而非方法。

5. **禁止覆盖基类非抽象方法**
   子类只能实现基类中声明的抽象方法，不允许覆盖基类中已实现的非抽象方法。
   基类负责维护对多个实现的一致性，非抽象方法的变更应向基类申请。

6. **禁止从抽象子类实例化**
   继承自 Onion 的抽象基类作为唯一入口，其抽象子类
   不应直接实例化。用户应导入具体实现类，然后通过基类来访问功能。

详见 onion_meta.py 模块文档字符串获取完整示例。

==============================================================================
多文件示例
==============================================================================

`onion.examples.calculator` 目录包含一个完整的多文件示例，演示：
- 如何将实现拆分到多个文件（basic + ext）
- 如何动态切换不同实现（int vs float）
- 如何使用 OnionUnload 清理和重新加载

查看方式:
    from onion.examples.calculator import Calculator
    # 或查看源码: python/Lib/site-packages/onion/examples/calculator/
==============================================================================
"""
from typing import List, Dict, Any

from onion.onion_base import Onion
from onion.onion_meta import OnionMeta, OnionViolationError
from abc import abstractmethod

__all__ = [
    'Onion', 'OnionMeta', 'OnionViolationError', 'abstractmethod',
    'OnionUnload', 'OnionProviders', 'OnionCompile'
]


import sys
import types


def OnionUnload(core_class, *modules):
    """
    完全卸载指定核心类的实现，重置为未编译状态，并清理相关模块

    :param core_class: 继承自 Onion 的核心类（如 AICore）
    :type core_class: type
    :param modules: 要清理的模块，可以是模块对象或模块名字符串，如 'ai.kimi_ai' 或 <module>
                      如果不指定，则自动卸载所有已注册实现类所在的模块
    :type modules: str or types.ModuleType

    :usage:
        from ai.ai_onion import AICore
        from onion import OnionUnload

        # 方式1: 传递模块名字符串
        OnionUnload(AICore, 'ai.kimi_ai')

        # 方式2: 传递模块对象
        import ai.kimi_ai
        OnionUnload(AICore, ai.kimi_ai)

        # 方式3: 不指定模块，自动卸载所有已注册实现类所在的模块
        OnionUnload(AICore)

        from ai import deepseek_ai  # 自动注册新实现
        ai = AICore()
    """
    if isinstance(core_class, OnionMeta):
        # 将模块对象转换为模块名字符串
        module_names = []
        for mod in modules:
            if isinstance(mod, types.ModuleType):
                module_names.append(mod.__name__)
            elif isinstance(mod, str):
                module_names.append(mod)
            else:
                raise TypeError(f"Expected module or str, got {type(mod).__name__}")
        core_class.OnionUnload(*module_names)
    else:
        raise TypeError(f"Expected Onion subclass, got {type(core_class).__name__}")


def OnionProviders(core_class):
    """
    获取已注册的实现类信息列表 (Onion模块级函数)

    返回每个实现类的模块信息、提供的抽象属性和抽象方法。
    与 OnionUnload 配套使用，用于查询当前注册状态。

    :param core_class: 继承自 Onion 的核心类（如 AICore）
    :type core_class: type

    :returns: 实现类信息列表，每个元素包含：
        - module: 模块路径 (str)
        - class_name: 类名 (str)
        - attrs: 提供的抽象属性列表 (List[str])
        - methods: 提供的抽象方法列表 (List[str])
    :rtype: List[Dict[str, Any]]

    :usage:
        from ai.ai_onion import AICore
        from onion import OnionGetProviders, OnionUnload

        # 查询已注册的实现
        providers = OnionGetProviders(AICore)
        print(providers)
        # [{'module': 'ai.models.kimi_ai', 'class_name': 'KimiAI', ...}]

        # 卸载后查询为空
        OnionUnload(AICore)
        providers = OnionGetProviders(AICore)  # []

    :note:
        该函数只返回在实现类中**显式定义**的属性和方法（即在类 __dict__ 中的），
        不包括从基类继承的。
    """
    if not isinstance(core_class, OnionMeta):
        raise TypeError(f"Expected Onion subclass, got {type(core_class).__name__}")

    # 获取核心类中定义的抽象属性和方法
    # 注意：编译后 __abstractmethods__ 会被清空，所以我们只能获取当前可用的信息
    abstract_names = getattr(core_class, '__abstractmethods__', None)

    # 收集已注册的实现类信息
    result = []
    # 获取已注册的子类列表（通过 _get_onion_subs 方法）
    registered_subs = core_class._get_onion_subs()

    for impl_class in registered_subs:
        info = {
            'module': impl_class.__module__,
            'class_name': impl_class.__name__,
        }

        # 收集实现的抽象成员信息
        # 策略：检查子类 __dict__ 中定义的成员（即显式实现的）
        if abstract_names:
            # __abstractmethods__ 可用（编译前）
            abstract_attrs = []
            abstract_methods = []

            for name in abstract_names:
                if name.startswith('_'):
                    continue
                attr = getattr(core_class, name, None)
                if isinstance(attr, property):
                    abstract_attrs.append(name)
                elif callable(attr):
                    abstract_methods.append(name)

            # 只收集在该类中显式定义的属性和方法
            info['attrs'] = [a for a in abstract_attrs if a in impl_class.__dict__]
            info['methods'] = [m for m in abstract_methods if m in impl_class.__dict__]
        else:
            # __abstractmethods__ 已清空（编译后）
            # 通过比较子类和基类的 __dict__ 来确定实现了哪些
            base_attrs = set(dir(core_class))
            impl_attrs = []
            impl_methods = []

            for name in impl_class.__dict__:
                if name.startswith('_'):
                    continue
                attr = getattr(impl_class, name, None)
                if isinstance(attr, property):
                    impl_attrs.append(name)
                elif callable(attr):
                    impl_methods.append(name)

            info['attrs'] = impl_attrs
            info['methods'] = impl_methods

        result.append(info)

    return result


def OnionCompile(core_class):
    """
    手动触发编译检查 (Onion模块级函数)

    Onion架构默认在首次实例化时自动编译。此函数允许在实例化前
    手动触发编译，用于提前检查实现类是否完整。

    :param core_class: 继承自 Onion 的核心类（如 AICore）
    :type core_class: type

    :raises OnionViolationError: 如果实现类违反架构约定

    :usage:
        from ai.ai_onion import AICore
        from ai.models.kimi_ai import KimiAI  # 导入即注册
        from onion import OnionCompile

        # 手动编译检查（可选）
        OnionCompile(AICore)

        # 正常实例化
        ai = AICore()

    :note:
        如果已编译，再次调用此函数不会有任何效果。
    """
    if not isinstance(core_class, OnionMeta):
        raise TypeError(f"Expected Onion subclass, got {type(core_class).__name__}")

    core_class.onionCompile()


if __name__ == '__main__':
    """
    Quick demo of oarch onion architecture framework.

    This example shows how to define an abstract core class and
    implement it through separate implementation classes.
    """
    from abc import abstractmethod

    # Define abstract core class with onion metaclass
    class Calculator(Onion):
        @abstractmethod
        def add(self, a, b):
            """Abstract method for addition operation"""
            pass

        @abstractmethod
        def sub(self, a, b):
            """Abstract method for subtraction operation"""
            pass

    # Implementation class providing concrete methods
    class BasicCalculator(Calculator):
        def add(self, a, b):
            print(f"[DEBUG] BasicCalculator.add({a}, {b})")  # Debug output: method call trace
            return a + b

        def sub(self, a, b):
            print(f"[DEBUG] BasicCalculator.sub({a}, {b})")  # Debug output: method call trace
            return a - b

    # Advanced implementation with additional functionality
    class ScientificCalculator(Calculator):
        def add(self, a, b):
            print(f"[DEBUG] ScientificCalculator.add({a}, {b})")  # Debug output: method call trace
            return a + b

        def sub(self, a, b):
            print(f"[DEBUG] ScientificCalculator.sub({a}, {b})")  # Debug output: method call trace
            return a - b

    '''
    You can import BasicCalculator or ScientificCalculator to use different implementations.
    In here, we define both BasicCalculator and ScientificCalculator. This will cause a warning, and shouldn't be imported together in production.

    ** Note:
    This is an engineering-level dynamic feature selection mechanism; therefore, it does not support runtime switching of implementation classes. If you need such behavior, consider using the 'pluggy'.
    '''


    # Create instance and test functionality
    print("=== oarch Onion Architecture Demo ===")
    calc = Calculator()

    print("\nTesting basic operations:")
    result1 = calc.add(10, 5)
    print(f"10 + 5 = {result1}")

    result2 = calc.sub(10, 5)
    print(f"10 - 5 = {result2}")

    print("\n=== Demo completed successfully ===")
