
'''
方法知识获取模块
1. 输入：问题 + 帖子信息 + 账号人设信息
2. 将输入的问题转化成query，调用大模型，prompt在 function_knowledge_generate_query_prompt.md 中
3. 从已有方法工具库中尝试选择合适的方法工具（调用大模型执行，prompt在 function_knowledge_select_tools_prompt.md 中），如果有，则返回选择的方法工具，否则：
    - 调用 multi_search_knowledge.py 获取知识
    - 返回新的方法工具知识
    - 异步从新方法知识中获取新工具(调用大模型执行，prompt在 function_knowledge_generate_new_tool_prompt.md 中)，调用工具库系统，接入新的工具
4. 调用选择的方法工具执行验证，返回工具执行结果
'''

import os
import sys
import json
import threading
from loguru import logger
import re

# 设置路径以便导入工具类
current_dir = os.path.dirname(os.path.abspath(__file__))
root_dir = os.path.dirname(current_dir)
sys.path.insert(0, root_dir)

from utils.gemini_client import generate_text
from knowledge_v2.tools_library import call_tool, save_tool_info, get_all_tool_infos, get_tool_info, get_tool_params
from knowledge_v2.multi_search_knowledge import get_knowledge as get_multi_search_knowledge
from knowledge_v2.cache_manager import CacheManager

class FunctionKnowledge:
    """方法知识获取类"""
    
    def __init__(self, use_cache: bool = True):
        """
        初始化
        
        Args:
            use_cache: 是否启用缓存，默认启用
        """
        logger.info("=" * 80)
        logger.info("初始化 FunctionKnowledge - 方法知识获取入口")
        self.prompt_dir = os.path.join(current_dir, "prompt")
        self.use_cache = use_cache
        self.cache = CacheManager() if use_cache else None
        

        
        logger.info(f"缓存状态: {'启用' if use_cache else '禁用'}")
        logger.info("=" * 80)
        


    def _load_prompt(self, filename: str) -> str:
        """加载prompt文件内容"""
        prompt_path = os.path.join(self.prompt_dir, filename)
        if not os.path.exists(prompt_path):
            raise FileNotFoundError(f"Prompt文件不存在: {prompt_path}")
        with open(prompt_path, 'r', encoding='utf-8') as f:
            return f.read().strip()

    def generate_query(self, question: str, post_info: str, persona_info: str) -> str:
        """
        生成查询语句
        
        Returns:
            str: 生成的查询语句
        """
        logger.info(f"[步骤1] 生成Query...")
        
        # 组合问题的唯一标识
        combined_question = f"{question}||{post_info}||{persona_info}"
        
        try:
            prompt_template = self._load_prompt("function_generate_query_prompt.md")
            prompt = prompt_template.format(
                question=question,
                post_info=post_info,
                persona_info=persona_info
            )

            # 尝试从缓存读取
            if self.use_cache:
                cached_data = self.cache.get(combined_question, 'function_knowledge', 'generated_query.json')
                if cached_data:
                    query = cached_data.get('query', cached_data.get('response', ''))
                    logger.info(f"✓ 使用缓存的Query: {query}")
                    return query
            
            logger.info("→ 调用Gemini生成Query...")
            query = generate_text(prompt=prompt)
            query = query.strip()
            
            logger.info(f"✓ 生成Query: {query}")
            
            # 保存到缓存（包含完整的prompt和response）
            if self.use_cache:
                query_data = {
                    "prompt": prompt,
                    "response": query,
                    "query": query
                }
                self.cache.set(combined_question, 'function_knowledge', 'generated_query.json', query_data)
            
            return query
        except Exception as e:
            logger.error(f"✗ 生成Query失败: {e}")
            return question  # 降级使用原问题

    def select_tool(self, combined_question: str, query: str) -> str:
        """
        选择合适的工具
        
        Returns:
            str: 工具名称，如果没有合适的工具则返回"None"
        """
        logger.info(f"[步骤2] 选择工具...")
        
        try:
            all_tool_infos = self._load_prompt("all_tools_infos.md")
            if not all_tool_infos:
                logger.info("  工具库为空，无可用工具")
                return "None"
            
            prompt_template = self._load_prompt("function_knowledge_select_tools_prompt.md")
            prompt = prompt_template.replace("{all_tool_infos}", all_tool_infos).replace("query", query)

            # 尝试从缓存读取
            if self.use_cache:
                cached_data = self.cache.get(combined_question, 'function_knowledge', 'selected_tool.json')
                if cached_data:
                    result_json = cached_data.get('response', {})
                    logger.info(f"✓ 使用缓存的工具: {result_json}")
                    return result_json
            
            logger.info("→ 调用Gemini选择工具...")
            result = generate_text(prompt=prompt)
            result = self.extract_and_validate_json(result)
            if not result:
                logger.error("✗ 选择工具失败: 无法提取有效JSON")
                return "None"

            result_json = json.loads(result)

            logger.info(f"✓ 选择结果: {result_json.get('工具名', 'None')}")
            
            # 保存到缓存（包含完整的prompt和response）
            if self.use_cache:
                tool_data = {
                    "prompt": prompt,
                    "response": result_json
                }
                self.cache.set(combined_question, 'function_knowledge', 'selected_tool.json', tool_data)
            
            return result_json
        except Exception as e:
            logger.error(f"✗ 选择工具失败: {e}")
            return "None"

    def extract_and_validate_json(self, text: str):
        """
        从字符串中提取 JSON 部分，并返回标准的 JSON 字符串。
        如果无法提取或解析失败，返回 None (或者你可以改为抛出异常)。
        """
        # 1. 使用正则表达式寻找最大的 JSON 块
        # r"(\{[\s\S]*\}|\[[\s\S]*\])" 的含义：
        # - \{[\s\S]*\} : 匹配以 { 开头，} 结尾的最长字符串（[\s\S] 包含换行符）
        # - | : 或者
        # - \[[\s\S]*\] : 匹配以 [ 开头，] 结尾的最长字符串（处理 JSON 数组）
        match = re.search(r"(\{[\s\S]*\}|\[[\s\S]*\])", text)

        if match:
            json_str = match.group(0)
            try:
                # 2. 尝试解析提取出的字符串，验证是否为合法 JSON
                parsed_json = json.loads(json_str)

                # 3. 重新转储为标准字符串 (去除原本可能存在的缩进、多余空格等)
                # ensure_ascii=False 保证中文不会变成 \uXXXX
                return json.dumps(parsed_json, ensure_ascii=False)

            except json.JSONDecodeError as e:
                print(f"提取到了类似JSON的片段，但解析失败: {e}")
                return None
        else:
            print("未在文本中发现 JSON 结构")
            return None

    def extract_tool_params(self, combined_question: str, query: str, tool_id: str, tool_instructions: str) -> dict:
        """
        根据工具信息和查询提取调用参数
        
        Args:
            combined_question: 组合问题（用于缓存）
            tool_name: 工具名称
            query: 查询内容
            
        Returns:
            dict: 提取的参数字典
        """
        logger.info(f"[步骤3] 提取工具参数...")
        
        try:
            # 获取工具信息
            tool_params = get_tool_params(tool_id)
            if not tool_params:
                logger.warning(f"  ⚠ 未找到工具 {tool_id} 的信息，使用默认参数")
                return {"keyword": query}
            
            # 加载prompt
            prompt_template = self._load_prompt("function_knowledge_extract_tool_params_prompt.md")
            prompt = prompt_template.format(
                tool_mcp_name=tool_id,
                tool_instructions=tool_instructions,
                all_tool_params=tool_params
            )

            # 尝试从缓存读取
            if self.use_cache:
                cached_data = self.cache.get(combined_question, 'function_knowledge', 'extracted_params.json')
                if cached_data:
                    params = cached_data.get('params', {})
                    logger.info(f"✓ 使用缓存的参数: {params}")
                    return params
            
            # 调用LLM提取参数
            logger.info("  → 调用Gemini提取参数...")
            response_text = generate_text(prompt=prompt)
            
            # 解析JSON
            logger.info("  → 解析参数JSON...")
            try:
                # 清理可能的markdown标记
                response_text = response_text.strip()
                if response_text.startswith("```json"):
                    response_text = response_text[7:]
                if response_text.startswith("```"):
                    response_text = response_text[3:]
                if response_text.endswith("```"):
                    response_text = response_text[:-3]
                response_text = response_text.strip()
                
                params = json.loads(response_text)
                
                logger.info(f"✓ 提取参数成功: {params}")
                
                # 保存到缓存（包含完整的prompt和response）
                if self.use_cache:
                    params_data = {
                        "prompt": prompt,
                        "response": response_text,
                        "params": params
                    }
                    self.cache.set(combined_question, 'function_knowledge', 'extracted_params.json', params_data)
                
                return params
                
            except json.JSONDecodeError as e:
                logger.error(f"  ✗ 解析JSON失败: {e}")
                logger.error(f"  响应内容: {response_text}")
                # 降级：使用query作为keyword
                default_params = {"keyword": query}
                logger.warning(f"  使用默认参数: {default_params}")
                return default_params
                
        except Exception as e:
            logger.error(f"✗ 提取工具参数失败: {e}")
            # 降级：使用query作为keyword
            return {"keyword": query}

    def save_knowledge_to_file(self, knowledge: str, combined_question: str):
        """保存获取到的知识到文件"""
        try:
            logger.info("[保存知识] 开始保存知识到文件...")

            # 获取问题hash
            import hashlib
            question_hash = hashlib.md5(combined_question.encode('utf-8')).hexdigest()[:12]

            # 获取缓存目录（和execution_record.json同级）
            if self.use_cache and self.cache:
                cache_dir = os.path.join(self.cache.base_cache_dir, question_hash)
            else:
                cache_dir = os.path.join(os.path.dirname(__file__), '.cache', question_hash)

            os.makedirs(cache_dir, exist_ok=True)

            # 保存到knowledge.txt
            knowledge_file = os.path.join(cache_dir, 'knowledge.txt')
            with open(knowledge_file, 'w', encoding='utf-8') as f:
                f.write(knowledge)

            logger.info(f"✓ 知识已保存到: {knowledge_file}")
            logger.info(f"  知识长度: {len(knowledge)} 字符")

        except Exception as e:
            logger.error(f"✗ 保存知识失败: {e}")

    def organize_tool_result(self, tool_result: dict) -> dict:
        """
        组织工具调用结果，确保包含必要字段

        Args:
            tool_result: 原始工具调用结果

        Returns:
            dict: 组织后的工具调用结果
        """
        prompt_template = self._load_prompt("tool_result_prettify_prompt.md")
        prompt = prompt_template.format(
            input=tool_result,
        )
        organized_result = generate_text(prompt=prompt)
        organized_result = organized_result.strip()
        return organized_result

    def get_knowledge(self, question: str, post_info: str, persona_info: str) -> dict:
        """
        获取方法知识的主流程（重构后）
        
        Returns:
            dict: 完整的执行记录
        """
        import time
        timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
        start_time = time.time()
        
        logger.info("=" * 80)
        logger.info(f"Function Knowledge - 开始处理")
        logger.info(f"问题: {question}")
        logger.info(f"帖子信息: {post_info}")
        logger.info(f"人设信息: {persona_info}")
        logger.info("=" * 80)
        
        # 组合问题的唯一标识
        combined_question = f"{question}||{post_info}||{persona_info}"
        
        try:
            # 步骤1: 生成Query
            query = self.generate_query(question, post_info, persona_info)
            
            # 步骤2: 选择工具
            tool_info = self.select_tool(combined_question, query)
            # tool_name = tool_info.get("工具名")
            tool_id = tool_info.get("工具调用ID")
            tool_instructions = tool_info.get("使用方法")
            if tool_id and tool_instructions:
                # 路径A: 使用工具
                # 步骤3: 提取参数
                arguments = self.extract_tool_params(combined_question, query, tool_id, tool_instructions)

                # 步骤4: 调用工具
                logger.info(f"[步骤4] 调用工具: {tool_id}")
                
                # 检查工具调用缓存
                if self.use_cache:
                    cached_tool_call = self.cache.get(combined_question, 'function_knowledge', 'tool_call.json')
                    if cached_tool_call:
                        logger.info(f"✓ 使用缓存的工具调用结果")
                        response = cached_tool_call.get('response', {})
                        # tool_result = self.organize_tool_result(response)
                        # # 保存工具调用信息（包含工具名、入参、结果）
                        # tool_call_data = {
                        #     "tool_name": tool_id,
                        #     "arguments": arguments,
                        #     "result": tool_result,
                        #     "response": response
                        # }
                        # self.cache.set(combined_question, 'function_knowledge', 'tool_call.json', tool_call_data)
                    else:
                        logger.info(f"  → 调用工具，参数: {arguments}")
                        rs = call_tool(tool_id, arguments)
                        tool_result = self.organize_tool_result(rs)
                        # 保存工具调用信息（包含工具名、入参、结果）
                        tool_call_data = {
                            "tool_name": tool_id,
                            "arguments": arguments,
                            "result": tool_result,
                            "response": rs
                        }
                        self.cache.set(combined_question, 'function_knowledge', 'tool_call.json', tool_call_data)
                else:
                    logger.info(f"  → 调用工具，参数: {arguments}")
                    tool_result = call_tool(tool_id, arguments)

                logger.info(f"✓ 工具调用完成")
                
            else:
                # 路径B: 知识搜索
                logger.info("[步骤4] 未找到合适工具，调用 MultiSearch...")
                
                knowledge = get_multi_search_knowledge(query, cache_key=combined_question)
                
                # 异步保存知识到文件
                logger.info("[后台任务] 保存知识到文件...")
                threading.Thread(target=self.save_knowledge_to_file, args=(knowledge, combined_question)).start()
            
            # 计算执行时间
            execution_time = time.time() - start_time
            
            # 收集所有执行记录
            logger.info("=" * 80)
            logger.info("收集执行记录...")
            logger.info("=" * 80)
            
            from knowledge_v2.execution_collector import collect_and_save_execution_record
            
            execution_record = collect_and_save_execution_record(
                combined_question,
                {
                    "question": question,
                    "post_info": post_info,
                    "persona_info": persona_info,
                    "timestamp": timestamp
                }
            )
            
            logger.info("=" * 80)
            logger.info(f"✓ Function Knowledge 完成")
            logger.info(f"  执行时间: {execution_record.get('metadata', {}).get('execution_time', 0):.2f}秒")
            logger.info("=" * 80 + "\n")
            
            return execution_record
            
        except Exception as e:
            logger.error(f"✗ 执行失败: {e}")
            import traceback
            logger.error(traceback.format_exc())
            
            # 即使失败也尝试收集记录
            try:
                execution_time = time.time() - start_time
                
                from knowledge_v2.execution_collector import collect_and_save_execution_record
                execution_record = collect_and_save_execution_record(
                    combined_question,
                    {
                        "question": question,
                        "post_info": post_info,
                        "persona_info": persona_info,
                        "timestamp": timestamp
                    }
                )
                return execution_record
            except Exception as collect_error:
                logger.error(f"收集执行记录也失败: {collect_error}")
                # 返回基本错误信息
                return {
                    "input": {
                        "question": question,
                        "post_info": post_info,
                        "persona_info": persona_info,
                        "timestamp": timestamp
                    },
                    "result": {
                        "type": "error",
                        "content": f"执行失败: {str(e)}"
                    },
                    "metadata": {
                        "errors": [str(e)]
                    }
                }

if __name__ == "__main__":
    # 测试代码
    question = "女儿的生日派对这个选题点怎么来的"
    post_info = "发帖时间：2025-09-16"
    persona_info = ""
    
    try:
        agent = FunctionKnowledge()
        execution_result = agent.get_knowledge(question, post_info, persona_info)
        print("=" * 50)
        print("执行结果:")
        print("=" * 50)
        print(json.dumps(execution_result, ensure_ascii=False, indent=2))
        print(f"\n完整JSON已保存到缓存目录")
    except Exception as e:
        logger.error(f"测试失败: {e}")
