"""
Klynx Agent - 提示词与上下文构建
PromptBuilderMixin 负责系统提示词生成、上下文 XML 构建、对话历史格式化
"""

import re
import os
from typing import Dict, List, Any

from langchain_core.messages import BaseMessage, HumanMessage, AIMessage

from klynx.agent.context_manager import TokenCounter


class PromptBuilderMixin:
    """提示词与上下文构建 Mixin - 被 KlynxAgent 继承"""

    # 系统提示词（指令部分）
    # Base system prompt is defined in agents.py

    # 上下文总结阈值（90%）
    CONTEXT_SUMMARIZE_THRESHOLD = 0.9

    def _get_system_prompt(self) -> str:
        """
        生成完整的系统提示词（基于已加载的工具和配置动态生成）
        
        Returns:
            完整的系统提示词字符串
        """
        # Base prompt is provided by concrete agent classes in agents.py.
        base_system_prompt = (getattr(self, "LOCKED_SYSTEM_PROMPT", "") or "").strip()

        prompt_parts = [base_system_prompt] if base_system_prompt else []

        runtime_append = (getattr(self, "_runtime_system_prompt_append", "") or "").strip()
        if runtime_append:
            prompt_parts.append(runtime_append)

        # OS ????
        os_lower = self.os_name.lower()
        if os_lower in ["linux", "macos", "mac"]:
            sys_msg = f"当前环境是 {self.os_name} 系统，不要使用 Windows 命令。阅读代码文件时，优先使用 Linux/Unix 命令进行搜索（例如 grep ）。"
        else:
            sys_msg = f"当前环境是 {self.os_name} 系统，不要使用 Linux/Unix 命令。阅读代码文件时，优先使用 windows 命令进行搜索（例如 findstr ）。"
        prompt_parts.append(f"<system_note>\n  <note>{sys_msg}</note>\n</system_note>")

        # Skills 提示（仅在安装了 skills 时注入）
        skills_prompt = (getattr(self, "_skills_prompt_cache", "") or "").strip()
        if skills_prompt:
            prompt_parts.append(skills_prompt)
        
        # 工具使用提示（仅在有工具加载时）
        if self._tool_prompts_cache:
            prompt_parts.append(self._tool_prompts_cache)
        
        # 记忆系统提示（仅在有 memory_dir 时）
        if self.memory_dir:
            memory_path = os.path.join(self.memory_dir, ".klynx", ".memory")
            rel_path = os.path.relpath(memory_path, self.working_dir) if self.working_dir else ".klynx/.memory"
            prompt_parts.append(f"""
<memory_system>
  <description>你拥有一个长期记忆系统，存储在 {rel_path} 文件中（XML格式）。</description>
  <usage>
    <item>当你需要查看历史记忆时，使用 read_file 读取 {rel_path}</item>
    <item>当你获取到重要的用户信息、项目上下文、设计决策等中长期有用的信息时，主动保存到 {rel_path}</item>
    <item>随着项目变化，及时更新 .memory 中的过时信息</item>
    <item>使用 replace_in_file 或 write_to_file 更新记忆内容</item>
  </usage>
  <format>
    XML格式，示例：
    &lt;memory&gt;
      &lt;entry key="user_preferences"&gt;内容&lt;/entry&gt;
      &lt;entry key="project_architecture"&gt;内容&lt;/entry&gt;
    &lt;/memory&gt;
  </format>
</memory_system>""")
        
        # TUI 交互指南（仅在 activate_tui_mode 工具被调用后注入）
        if getattr(self, '_tui_guide_loaded', False):
            prompt_parts.append(self._get_tui_guide())
        
        return "\n".join(prompt_parts)
    
    def _get_tui_guide(self) -> str:
        """返回 TUI 交互指南（由 activate_tui_mode 触发注入 system prompt）"""
        return """
<tui_interaction_guide>
  <description>你已激活 TUI 交互模式。以下是使用 TUI 工具的完整指南。</description>

  <workflow>
    <step number="1">
      <name>启动 TUI 应用</name>
      <details>
        使用 open_tui 启动目标应用：
        &lt;open_tui&gt;&lt;name&gt;会话名&lt;/name&gt;&lt;command&gt;应用命令&lt;/command&gt;&lt;/open_tui&gt;
        示例：&lt;open_tui&gt;&lt;name&gt;editor&lt;/name&gt;&lt;command&gt;vim test.py&lt;/command&gt;&lt;/open_tui&gt;
      </details>
    </step>
    <step number="2">
      <name>读取屏幕状态</name>
      <details>
        在每次操作后，使用 read_tui 查看当前屏幕：
        &lt;read_tui&gt;&lt;name&gt;editor&lt;/name&gt;&lt;/read_tui&gt;
        返回 XML 格式屏幕快照，包含每行文本和光标位置。
        **重要：每次 send_keys 后必须 read_tui 确认结果！**
      </details>
    </step>
    <step number="3">
      <name>发送按键</name>
      <details>
        使用 send_keys 发送输入。按键用空格分隔，支持特殊键：
        &lt;send_keys&gt;&lt;name&gt;editor&lt;/name&gt;&lt;keys&gt;按键序列&lt;/keys&gt;&lt;/send_keys&gt;
      </details>
    </step>
    <step number="4">
      <name>关闭会话</name>
      <details>完成后用 close_tui 关闭会话。</details>
    </step>
  </workflow>

  <key_reference>
    <category name="基础键">
      Enter, Tab, Escape (Esc), Backspace, Delete, Space
    </category>
    <category name="方向键">
      Up, Down, Left, Right, Home, End, PageUp, PageDown
    </category>
    <category name="Ctrl 组合">
      Ctrl-C (中断), Ctrl-D (EOF), Ctrl-Z (挂起), Ctrl-S (保存),
      Ctrl-A, Ctrl-E, Ctrl-K, Ctrl-W, Ctrl-U, Ctrl-R 等
    </category>
    <category name="功能键">
      F1 ~ F12
    </category>
  </key_reference>

  <send_keys_examples>
    <example desc="在 vim 中输入文本">i hello world Escape</example>
    <example desc="vim 保存退出">Escape :wq Enter</example>
    <example desc="vim 不保存退出">Escape :q! Enter</example>
    <example desc="Python REPL 计算">1+1 Enter</example>
    <example desc="向上翻页">PageUp</example>
    <example desc="发送中断信号">Ctrl-C</example>
    <example desc="连续方向键">Up Up Down Enter</example>
    <example desc="输入带空格文本（每个词是独立token）">hello world Enter</example>
  </send_keys_examples>

  <best_practices>
    <rule>每次 send_keys 后立即 read_tui 确认操作结果</rule>
    <rule>操作 vim 时先确认当前模式（NORMAL/INSERT/VISUAL），再决定按键</rule>
    <rule>屏幕底部状态栏通常包含模式、光标位置等关键信息</rule>
    <rule>如果屏幕没有预期变化，可能需要等待后再次 read_tui</rule>
    <rule>完成操作后始终 close_tui，释放资源</rule>
    <rule>对于 send_keys 中的普通文本，每个空格分隔的词会被直接拼接发送</rule>
  </best_practices>
</tui_interaction_guide>"""

    def _get_tools_prompt(self) -> str:
        """
        生成工具列表的 prompt 片段（含名称和说明）
        
        Returns:
            XML 格式的工具列表字符串
        """
        if not self.tools:
            return "无可用工具"
        
        lines = []
        for name, desc in self.tools.items():
            lines.append(f"  - {name}: {desc}")
        return "\n".join(lines)

    def _get_env_snapshot(self) -> str:
        """
        获取环境快照（XML格式）
        """
        # 生成文件树
        file_tree = self._generate_file_tree(self.working_dir, depth=2)
        
        snapshot = f"""<file_tree>
{file_tree}
</file_tree>"""
        return snapshot
    
    def _generate_file_tree(self, path: str, depth: int = 2, prefix: str = "") -> str:
        """生成文件树结构"""
        if depth < 0:
            return ""
        
        try:
            entries = sorted(os.listdir(path))
        except PermissionError:
            return f"{prefix}[无权限访问]"
        
        lines = []
        # 过滤隐藏文件和常见忽略目录
        ignore_patterns = {'.git', '__pycache__', 'node_modules', '.venv', 'venv', '.idea', '.vscode'}
        entries = [e for e in entries if e not in ignore_patterns and not e.startswith('.')]
        
        for i, entry in enumerate(entries):
            is_last = (i == len(entries) - 1)
            connector = "└── " if is_last else "├── "
            entry_path = os.path.join(path, entry)
            
            if os.path.isdir(entry_path):
                lines.append(f"{prefix}{connector}{entry}/")
                if depth > 0:
                    extension = "    " if is_last else "│   "
                    subtree = self._generate_file_tree(entry_path, depth - 1, prefix + extension)
                    if subtree:
                        lines.append(subtree)
            else:
                lines.append(f"{prefix}{connector}{entry}")
        
        return "\n".join(lines)

    def _parse_model_response(self, response) -> Dict[str, str]:
        """
        解析模型响应 - 支持 DeepSeek 思考模式
        
        DeepSeek 思考模式返回两个字段：
        - reasoning_content: 思维链内容（思考过程）
        - content: 最终回答内容
        
        Args:
            response: 模型返回的响应对象
            
        Returns:
            包含 reasoning_content 和 content 的字典
        """
        result = {
            "reasoning_content": "",
            "content": ""
        }
        
        # 获取消息对象
        message = response
        if hasattr(response, 'choices') and response.choices:
            message = response.choices[0].message
        
        # 提取 reasoning_content（DeepSeek 思考模式特有）
        if hasattr(message, 'reasoning_content') and message.reasoning_content:
            result["reasoning_content"] = message.reasoning_content
        elif hasattr(message, 'additional_kwargs'):
            # 备选：从 additional_kwargs 中获取
            result["reasoning_content"] = message.additional_kwargs.get('reasoning_content', '')
        
        # 提取 content
        if hasattr(message, 'content'):
            result["content"] = message.content or ""
        
        return result
    
    def _extract_reasoning_content(self, response) -> str:
        """
        提取 DeepSeek 模型的思考内容 (reasoning_content)
        
        支持多种获取方式：
        1. response.reasoning_content (直接属性 - LiteLLMResponse)
        2. response.additional_kwargs['reasoning_content']
        3. response.response_metadata['reasoning_content']
        
        Args:
            response: 模型响应对象
            
        Returns:
            思考内容字符串
        """
        # 方式1: 直接属性 (LiteLLMResponse)
        if hasattr(response, 'reasoning_content') and response.reasoning_content:
            return response.reasoning_content
        
        # 方式2: additional_kwargs
        if hasattr(response, 'additional_kwargs') and response.additional_kwargs:
            rc = response.additional_kwargs.get('reasoning_content', '')
            if rc:
                return rc
        
        # 方式3: response_metadata
        if hasattr(response, 'response_metadata') and response.response_metadata:
            rc = response.response_metadata.get('reasoning_content', '')
            if rc:
                return rc
        
        return ""

    def _build_context(self, state, include_history: bool = True, emit_stats: bool = True) -> str:
        """
        构建XML格式的上下文变量（完整注入，不截断）
        
        上下文包含：
        - 历史对话（可选）
        - 文档读取内容
        - 任务目标（如果已生成）
        - 环境信息
        
        只有当上下文为空时，才添加系统身份介绍
        
        Args:
            state: 当前状态
            include_history: 是否包含历史对话
            emit_stats: 是否输出上下文统计日志
            
        Returns:
            构建好的XML上下文字符串
        """
        # Token统计
        token_stats = {
            "system_identity": 0,
            "task_goal": 0,
            "environment": 0,
            "working_directory": 0,
            "progress_summary": 0,
            "conversation_history": 0,
            "context_summary": 0,
            "document_content": 0,
            "skill_context": 0,
        }
        
        # 判断上下文是否为空（没有任何实质内容）
        existing_context = state.get("context", "")
        current_task = state.get("current_task", "")
        progress = state.get("progress_summary", "")
        has_content = bool(existing_context.strip() or current_task.strip() or progress.strip())
        
        # 构建XML上下文
        xml_parts = ['<?xml version="1.0" encoding="UTF-8"?>\n<context>']
        
        # 总任务目标
        overall_goal = state.get("overall_goal", "")
        if overall_goal:
            escaped_goal = self._escape_xml(overall_goal)
            goal_xml = f"""  <overall_goal>
    {escaped_goal}
  </overall_goal>"""
            xml_parts.append(goal_xml)
        
        # 当前任务目标
        if current_task:
            escaped_task = self._escape_xml(current_task)
            task_xml = f"""  <current_task>
    {escaped_task}
  </current_task>"""
            xml_parts.append(task_xml)
            token_stats["task_goal"] = TokenCounter.estimate_tokens(task_xml)
        
        # 环境信息
        env_snapshot = state.get("env_snapshot", "")
        if env_snapshot:
            escaped_env = self._escape_xml(env_snapshot)
            env_xml = f"""  <environment>
    {escaped_env}
  </environment>"""
            xml_parts.append(env_xml)
            token_stats["environment"] = TokenCounter.estimate_tokens(env_xml)
        
        # 工作目录
        escaped_workdir = self._escape_xml(str(self.working_dir))
        workdir_xml = f"""  <working_directory>
    {escaped_workdir}
  </working_directory>"""
        xml_parts.append(workdir_xml)
        token_stats["working_directory"] = TokenCounter.estimate_tokens(workdir_xml)
        
        # 进度摘要
        if progress:
            escaped_progress = self._escape_xml(progress)
            progress_xml = f"""  <progress_summary>
    {escaped_progress}
  </progress_summary>"""
            xml_parts.append(progress_xml)
            token_stats["progress_summary"] = TokenCounter.estimate_tokens(progress_xml)
        
        # 对话历史（如果有总结，使用总结；否则使用完整历史）
        context_summary = state.get("context_summary", "")
        if include_history:
            if context_summary:
                # 使用已总结的上下文
                summary_xml = f"""  <context_summary>
    {self._escape_xml(context_summary)}
  </context_summary>"""
                xml_parts.append(summary_xml)
                token_stats["context_summary"] = TokenCounter.estimate_tokens(summary_xml)
                if emit_stats:
                    self._emit("info", "  [上下文] 使用已总结的历史")
            else:
                # 使用完整对话历史
                history_msgs = state.get("messages", [])
                if history_msgs:
                    history_xml = self._format_conversation_history_xml(history_msgs)
                    if history_xml:
                        full_history_xml = f"""  <conversation_history>
{history_xml}
  </conversation_history>"""
                        xml_parts.append(full_history_xml)
                        token_stats["conversation_history"] = TokenCounter.estimate_tokens(full_history_xml)
        
        # 已有上下文（文档读取内容等）
        if existing_context:
            escaped_context = self._escape_xml(existing_context)
            doc_xml = f"""  <document_content>
    {escaped_context}
  </document_content>"""
            xml_parts.append(doc_xml)
            token_stats["document_content"] = TokenCounter.estimate_tokens(doc_xml)

        # 已加载 skills 的 SKILL.md 上下文
        skill_context = state.get("skill_context", "")
        if skill_context:
            escaped_skill_context = self._escape_xml(skill_context)
            skill_xml = f"""  <skill_context>
    {escaped_skill_context}
  </skill_context>"""
            xml_parts.append(skill_xml)
            token_stats["skill_context"] = TokenCounter.estimate_tokens(skill_xml)
        
        xml_parts.append("</context>")
        context_xml = "\n".join(xml_parts)
        
        # 计算总token并显示统计
        total_tokens = sum(token_stats.values())
        usage_pct = (total_tokens / self.max_context_tokens) * 100
        
        if emit_stats:
            # 打印上下文用量统计
            self._emit("context_stats", f"[上下文统计] Token用量: {total_tokens:,} / {self.max_context_tokens:,} ({usage_pct:.1f}%)")
            
            # 显示各部分用量（仅显示非零部分）
            non_zero_stats = {k: v for k, v in token_stats.items() if v > 0}
            if non_zero_stats:
                stats_str = " | ".join([f"{k}: {v:,}" for k, v in non_zero_stats.items()])
                self._emit("context_stats", f"  细分: {stats_str}")
            
            # 警告：接近上限
            if usage_pct > 80:
                self._emit("warning", "⚠️ 警告: 上下文用量超过80%，建议清理历史对话")
        
        return context_xml
    
    def _escape_xml(self, text: str) -> str:
        """转义XML特殊字符"""
        if not text:
            return ""
        text = text.replace("&", "&amp;")
        text = text.replace("<", "&lt;")
        text = text.replace(">", "&gt;")
        text = text.replace('"', "&quot;")
        text = text.replace("'", "&apos;")
        return text
    
    def _format_conversation_history_xml(self, messages: List[BaseMessage]) -> str:
        """
        格式化对话历史为XML格式
        
        对AI消息进行压缩：只保留工具调用（包含操作细节）和完成标记，
        去掉Agent的分析/思考文本，避免Agent重新读取自己的分析并误判为用户请求。
        对用户/工具结果消息则完整保留。
        
        Args:
            messages: 消息列表
            
        Returns:
            XML格式的对话历史字符串
        """
        if not messages:
            return ""
        
        # 工具调用相关的XML标签（需要保留的）
        tool_tags = [
            'read_file', 'write_to_file', 'execute_command', 
            'list_directory', 'replace_in_file', 'create_directory',
            'preview_file', 'search_in_files', 'load_skill', 'attempt_completion'
        ]
        
        entries = []
        for i, msg in enumerate(messages):
            if isinstance(msg, HumanMessage):
                # 用户消息和工具结果：过滤无意义的消息
                content = msg.content if hasattr(msg, 'content') else str(msg)
                stripped = content.strip()
                # 过滤仅包含"继续"、"好"、"ok"等无信息量的消息
                if stripped.lower() in ('', '继续', '好', '好的', 'ok', 'yes', '易', '嗯', '确认', 'continue', 'go', 'y'):
                    continue
                escaped_content = self._escape_xml(stripped)
                entries.append(f'    <message index="{i}" role="user">\n      {escaped_content}\n    </message>')
            elif isinstance(msg, AIMessage):
                # AI消息：只提取工具调用部分，去掉分析/思考文本
                content = msg.content if hasattr(msg, 'content') else str(msg)
                compressed = self._compress_ai_message(content, tool_tags)
                if compressed:
                    escaped_content = self._escape_xml(compressed.strip())
                    entries.append(f'    <message index="{i}" role="assistant" compressed="true">\n      {escaped_content}\n    </message>')
            else:
                content = msg.content if hasattr(msg, 'content') else str(msg)
                escaped_content = self._escape_xml(content.strip())
                entries.append(f'    <message index="{i}" role="system">\n      {escaped_content}\n    </message>')
        
        return "\n".join(entries)
    
    def _compress_ai_message(self, content: str, tool_tags: list) -> str:
        """
        压缩AI消息：保留工具调用XML标签及其内容，去掉分析/思考文本。
        
        保留的内容：工具调用（包括写入的代码、修改的内容等）和attempt_completion。
        去掉的内容：<step>、<thinking>、<task_goal>及其他纯文本分析。
        
        Args:
            content: AI消息的完整内容
            tool_tags: 需要保留的工具标签列表
            
        Returns:
            压缩后的内容字符串
        """
        preserved_parts = []
        
        for tag in tool_tags:
            # 匹配完整的工具调用标签（包括子元素格式和属性格式）
            # 子元素格式: <tag>...</tag>
            pattern = rf'<{tag}[\s>].*?</{tag}>'
            matches = re.findall(pattern, content, re.DOTALL)
            for match in matches:
                preserved_parts.append(match)
        
        if not preserved_parts:
            return ""
        
        return "[Agent操作记录]\n" + "\n".join(preserved_parts)
    
    def _format_conversation_history(self, messages: List[BaseMessage]) -> str:
        """
        格式化对话历史（完整注入，不截断）
        
        Args:
            messages: 消息列表
            
        Returns:
            格式化的对话历史字符串
        """
        history_parts = []
        
        for msg in messages:
            if isinstance(msg, HumanMessage):
                content = msg.content if hasattr(msg, 'content') else str(msg)
                history_parts.append(f"[User/Tool Result]: {content}")
            elif isinstance(msg, AIMessage):
                content = msg.content if hasattr(msg, 'content') else str(msg)
                history_parts.append(f"[Assistant]: {content}")
        
        return "\n".join(history_parts)
    
    def _quick_summarize_messages(self, messages: list, max_entries: int = 5) -> str:
        """
        快速本地总结消息历史（不调用LLM）
        
        提取最近的用户输入和AI回复摘要，用于新一轮对话的上下文继承。
        避免将完整的旧消息注入prompt导致Agent误解为当前任务。
        
        Args:
            messages: 消息列表
            max_entries: 最多提取的消息对数
            
        Returns:
            简短的对话摘要字符串
        """
        if not messages:
            return ""
        
        summary_parts = []
        # 只取最近的消息
        recent = messages[-max_entries * 2:] if len(messages) > max_entries * 2 else messages
        
        for msg in recent:
            if isinstance(msg, HumanMessage):
                content = getattr(msg, 'content', str(msg))
                # 截断长内容
                snippet = content[:150] + "..." if len(content) > 150 else content
                summary_parts.append(f"用户: {snippet}")
            elif isinstance(msg, AIMessage):
                content = getattr(msg, 'content', str(msg))
                # AI回复只取前100字符
                snippet = content[:100] + "..." if len(content) > 100 else content
                summary_parts.append(f"助手: {snippet}")
        
        if not summary_parts:
            return ""
        
        return "[之前的对话摘要]\n" + "\n".join(summary_parts)
