# Copyright (c) 2025, JINGROW and contributors
# For license information, please see license.txt

"""
钩子加载器 - 从 hooks.py 配置文件加载钩子
支持应用级钩子配置
"""

import os
import importlib
import inspect
import types
import logging
from typing import Dict, List, Any, Optional, Union
from .registry import registry

# 受支持的 hooks.py 键（仅这些键会被加载与注册）
SUPPORTED_HOOK_KEYS = {
    'pg_events',
    'scheduler_events',
    'before_request',
    'after_request',
    'before_job',
    'after_job',
}

logger = logging.getLogger(__name__)


class HookLoader:
    """
    钩子加载器 - 加载 hooks.py 配置文件中的钩子
    
    支持的钩子类型：
    - pg_events: 页面生命周期钩子
    - scheduler_events: 定时任务钩子
    - before_request / after_request: 请求钩子
    - before_job / after_job: 后台任务钩子
    - 其他自定义钩子
    """
    
    def __init__(self):
        self._loaded_apps: set = set()
        self._cache: Dict[str, Any] = {}
    
    def load_app_hooks(self, app_name: Optional[str] = None) -> Dict[str, Any]:
        """
        加载应用钩子配置
        
        :param app_name: 应用名称，如果为 None 则加载所有已安装应用
        :return: 钩子配置字典
        """
        if app_name:
            return self._load_single_app_hooks(app_name)
        
        # 加载所有应用的钩子
        hooks = {}
        
        # 获取已安装应用列表
        try:
            from jingrow.utils.app_manager import get_installed_apps
            installed_apps = get_installed_apps()
        except Exception:
            # 如果获取失败，至少加载 jingrow
            installed_apps = ['jingrow']
        
        for app in installed_apps:
            try:
                app_hooks = self._load_single_app_hooks(app)
                # 合并钩子（后加载的应用钩子会追加到列表中）
                for key, value in app_hooks.items():
                    if key in hooks:
                        # 如果已存在，追加到列表
                        if isinstance(hooks[key], list) and isinstance(value, list):
                            hooks[key].extend(value)
                        elif isinstance(hooks[key], list):
                            hooks[key].append(value)
                        elif isinstance(value, list):
                            hooks[key] = [hooks[key]] + value
                        else:
                            hooks[key] = [hooks[key], value]
                    else:
                        hooks[key] = value
            except Exception as e:
                logger.warning(f"加载应用 {app} 的钩子失败: {e}")
        
        return hooks
    
    def _load_single_app_hooks(self, app_name: str) -> Dict[str, Any]:
        """加载单个应用的钩子"""
        cache_key = f"app_hooks_{app_name}"
        
        # 检查缓存
        if cache_key in self._cache:
            return self._cache[cache_key]
        
        try:
            # 导入 hooks 模块
            hooks_module = importlib.import_module(f"{app_name}.hooks")
            
            # 仅提取受支持的钩子键，忽略应用元信息（如 app_name、app_title 等）
            hooks: Dict[str, Any] = {}

            for key in SUPPORTED_HOOK_KEYS:
                if hasattr(hooks_module, key):
                    hooks[key] = getattr(hooks_module, key)
            
            logger.debug(f"加载应用 {app_name} 的钩子: {list(hooks.keys())}")
            
            # 缓存结果
            self._cache[cache_key] = hooks
            
            return hooks
            
        except ImportError as e:
            logger.debug(f"应用 {app_name} 没有 hooks.py 模块: {e}")
            return {}
        except Exception as e:
            logger.error(f"加载应用 {app_name} 的钩子失败: {e}", exc_info=True)
            return {}
    
    def register_loaded_hooks(self, app_name: Optional[str] = None) -> None:
        """
        注册已加载的钩子到注册表
        
        :param app_name: 应用名称，如果为 None 则注册所有应用
        """
        hooks_config = self.load_app_hooks(app_name)
        
        # 处理 pg_events（页面生命周期钩子）
        if 'pg_events' in hooks_config:
            self._register_pg_events(hooks_config['pg_events'])
        
        # 处理 scheduler_events（定时任务钩子）
        if 'scheduler_events' in hooks_config:
            self._register_scheduler_events(hooks_config['scheduler_events'])
        
        # 处理其他受支持的通用钩子（目前仅支持 request/job 级别）
        for hook_name in ('before_request', 'after_request', 'before_job', 'after_job'):
            hook_value = hooks_config.get(hook_name)
            if not hook_value:
                continue
            # 为这些钩子创建统一命名并注册
            signal = registry.register(f"{hook_name}")
            if isinstance(hook_value, list):
                self._register_handlers(signal, hook_value)
            elif isinstance(hook_value, str):
                self._register_handler(signal, hook_value)
    
    def _register_pg_events(self, pg_events: Dict[str, Any]) -> None:
        """注册页面生命周期钩子"""
        for pagetype, events in pg_events.items():
            if isinstance(events, dict):
                for event_name, handlers in events.items():
                    # 创建钩子名称：如 "page.User.on_update"
                    hook_name = f"page.{pagetype}.{event_name}"
                    signal = registry.register(hook_name)

                    # sender 处理：当 pagetype 为 "*" 时表示所有 sender
                    sender_value = None if pagetype == "*" else pagetype

                    # 注册处理器
                    if isinstance(handlers, list):
                        for handler_path in handlers:
                            self._register_handler(signal, handler_path, sender=sender_value)
                    elif isinstance(handlers, str):
                        self._register_handler(signal, handlers, sender=sender_value)
    
    def _register_scheduler_events(self, scheduler_events: Dict[str, Any]) -> None:
        """注册定时任务钩子"""
        for event_type, handlers in scheduler_events.items():
            if event_type == 'cron':
                # Cron 表达式钩子
                for cron_expr, handler_list in handlers.items():
                    hook_name = f"scheduler.cron.{cron_expr}"
                    signal = registry.register(hook_name)
                    self._register_handlers(signal, handler_list)
            else:
                # 其他定时任务类型（hourly, daily, weekly 等）
                hook_name = f"scheduler.{event_type}"
                signal = registry.register(hook_name)
                self._register_handlers(signal, handlers)
    
    def _register_hook(self, hook_name: str, hook_value: Any) -> None:
        """注册通用钩子"""
        signal = registry.register(hook_name)
        
        if isinstance(hook_value, list):
            self._register_handlers(signal, hook_value)
        elif isinstance(hook_value, str):
            self._register_handler(signal, hook_value)
        else:
            # 其他类型的值，可能需要特殊处理
            logger.warning(f"不支持的钩子值类型: {hook_name}={type(hook_value)}")
    
    def _register_handlers(self, signal: Any, handlers: List[str]) -> None:
        """注册多个处理器"""
        for handler_path in handlers:
            self._register_handler(signal, handler_path)
    
    def _register_handler(self, signal: Any, handler_path: str, sender: Optional[str] = None) -> None:
        """
        注册单个处理器
        
        :param signal: Signal 对象
        :param handler_path: 处理器路径，如 "app.module.function"
        :param sender: 发送者类型
        """
        try:
            # 动态导入处理器函数
            handler = self._import_handler(handler_path)
            if handler:
                signal.connect(handler=handler, sender=sender)
                logger.debug(f"注册处理器: {handler_path} -> {signal}")
        except Exception as e:
            logger.error(f"注册处理器失败 {handler_path}: {e}", exc_info=True)
    
    def _import_handler(self, handler_path: str) -> Optional[Any]:
        """导入处理器函数"""
        try:
            # 分割模块路径和函数名
            parts = handler_path.rsplit('.', 1)
            if len(parts) != 2:
                logger.error(f"无效的处理器路径格式: {handler_path}")
                return None
            
            module_path, function_name = parts
            module = importlib.import_module(module_path)
            handler = getattr(module, function_name, None)
            
            if handler is None:
                logger.error(f"处理器函数不存在: {handler_path}")
                return None
            
            if not callable(handler):
                logger.error(f"处理器不是可调用对象: {handler_path}")
                return None
            
            return handler
            
        except ImportError as e:
            logger.error(f"导入处理器模块失败: {handler_path} - {e}")
            return None
        except Exception as e:
            logger.error(f"导入处理器失败: {handler_path} - {e}")
            return None
    
    def clear_cache(self, app_name: Optional[str] = None) -> None:
        """清除缓存"""
        if app_name:
            cache_key = f"app_hooks_{app_name}"
            self._cache.pop(cache_key, None)
        else:
            self._cache.clear()


# 全局加载器实例
_loader_instance = HookLoader()


def load_app_hooks(app_name: Optional[str] = None) -> Dict[str, Any]:
    """
    便捷函数：加载应用钩子配置
    
    :param app_name: 应用名称
    :return: 钩子配置字典
    """
    return _loader_instance.load_app_hooks(app_name)


def register_loaded_hooks(app_name: Optional[str] = None) -> None:
    """
    便捷函数：注册已加载的钩子
    
    :param app_name: 应用名称
    """
    _loader_instance.register_loaded_hooks(app_name)


# 保留 HookLoader 类定义以便导入
# HookLoader 已在上面定义

