import asyncio
import json
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

try:
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )
except ImportError:
    import sys
    project_root = Path(__file__).resolve().parents[2]
    if str(project_root) not in sys.path:
        sys.path.insert(0, str(project_root))
    from config import (
        OUTPUT_FORMATS, DATA_DIR, PROMPTS_DIR, NEED_CONFIRMATION,
        MAX_TERMINALS, TERMINAL_BUFFER_SIZE, TERMINAL_DISPLAY_SIZE,
        MAX_READ_FILE_CHARS, READ_TOOL_DEFAULT_MAX_CHARS,
        READ_TOOL_DEFAULT_CONTEXT_BEFORE, READ_TOOL_DEFAULT_CONTEXT_AFTER,
        READ_TOOL_MAX_CONTEXT_BEFORE, READ_TOOL_MAX_CONTEXT_AFTER,
        READ_TOOL_DEFAULT_MAX_MATCHES, READ_TOOL_MAX_MATCHES,
        READ_TOOL_MAX_FILE_SIZE,
        TERMINAL_SANDBOX_MOUNT_PATH,
        TERMINAL_SANDBOX_MODE,
        TERMINAL_SANDBOX_CPUS,
        TERMINAL_SANDBOX_MEMORY,
        PROJECT_MAX_STORAGE_MB,
        CUSTOM_TOOLS_ENABLED,
    )

from modules.file_manager import FileManager
from modules.search_engine import SearchEngine
from modules.terminal_ops import TerminalOperator
from modules.memory_manager import MemoryManager
from modules.terminal_manager import TerminalManager
from modules.todo_manager import TodoManager
from modules.sub_agent import SubAgentManager
from modules.webpage_extractor import extract_webpage_content, tavily_extract
from modules.multi_agent.prompts import build_available_agents_prompt
from modules.ocr_client import OCRClient
from modules.easter_egg_manager import EasterEggManager
from modules.personalization_manager import (
    load_personalization_config,
    build_personalization_prompt,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX,
    RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT,
)
from modules.skills_manager import (
    get_skills_catalog,
    build_skills_list,
    merge_enabled_skills,
    build_skills_prompt,
    infer_private_skills_dir,
)
from modules.custom_tool_registry import CustomToolRegistry, build_default_tool_category
from modules.custom_tool_executor import CustomToolExecutor

try:
    from config.limits import THINKING_FAST_INTERVAL
except ImportError:
    THINKING_FAST_INTERVAL = 10

from modules.container_monitor import collect_stats, inspect_state
from core.tool_config import TOOL_CATEGORIES
from utils.api_client import DeepSeekClient
from utils.context_manager import ContextManager, AUTO_SHALLOW_PLACEHOLDER
from utils.host_workspace_debug import write_host_workspace_debug
from utils.tool_result_formatter import format_tool_result_for_context
from utils.logger import setup_logger
from config.model_profiles import (
    get_model_profile,
    get_model_prompt_replacements,
    get_model_context_window,
    model_supports_image,
    model_supports_video,
)

logger = setup_logger(__name__)
DISABLE_LENGTH_CHECK = True


class MessagesMixin:
    """MainTerminalContextMixin messages 能力 mixin。"""

    def build_messages(self, context: Dict, user_input: str) -> List[Dict]:
            """构建消息列表（添加终端内容注入）"""
            try:
                file_tree_preview = (context.get("project_info", {}).get("file_tree") or "").splitlines()
                write_host_workspace_debug(
                    "main_terminal.build_messages.context_snapshot",
                    terminal_id=id(self),
                    terminal_project_path=str(getattr(self, "project_path", "")),
                    context_project_path=str(getattr(getattr(self, "context_manager", None), "project_path", "")),
                    project_info_path=str(context.get("project_info", {}).get("path", "")),
                    file_tree_first_line=file_tree_preview[0] if file_tree_preview else "",
                    current_conversation_id=getattr(getattr(self, "context_manager", None), "current_conversation_id", None),
                )
            except Exception:
                pass
            # 根据当前模型多模态能力选择系统提示。system prompt 按对话冻结；
            # 工具 schema 保持动态，由 define_tools 每轮根据真实状态生成。
            current_model = getattr(self, "model_key", None)
            prompt_name = "main_system_vl" if (model_supports_image(current_model) or model_supports_video(current_model)) else "main_system"
            model_key = getattr(self, "model_key", None)
            prompt_replacements = get_model_prompt_replacements(model_key)

            is_multi_agent_mode = bool(getattr(self, "multi_agent_mode", False))

            def _build_main_system_prompt() -> str:
                if is_multi_agent_mode:
                    # 多智能体模式下主 prompt 使用 Team Leader 专属模板
                    try:
                        from modules.multi_agent.prompts import _load_template
                        return _load_template("master")
                    except Exception as exc:
                        logger.warning(f"[messages] 加载多智能体主 prompt 失败: {exc}")
                system_prompt_template = self.load_prompt(prompt_name)
                # main_system.txt / main_system_vl.txt 仅使用 {model_description}
                return system_prompt_template.format(
                    model_description=prompt_replacements.get("model_description", "")
                )

            main_system_frozen_key = (
                "frozen_main_system_prompt_multi_agent"
                if is_multi_agent_mode
                else "frozen_main_system_prompt"
            )
            system_prompt = self._get_or_init_frozen_prompt(
                main_system_frozen_key,
                _build_main_system_prompt,
            )

            messages = [
                {"role": "system", "content": system_prompt}
            ]

            personalization_config = getattr(self.context_manager, "custom_personalization_config", None) or load_personalization_config(self.data_dir)
            shallow_replace_enabled = bool(personalization_config.get("auto_shallow_compress_enabled", False)) if isinstance(personalization_config, dict) else False

            # 顺序：主prompt → 权限模式 → 执行环境 → 最近对话 → 个性化配置 → 工作区信息 → AGENTS.md → skills → 记忆 → 自定义 → 禁用提示

            # 权限模式
            permission_mode_message = self._get_or_init_frozen_mode_prompt(
                "frozen_permission_prompt",
                self._build_permission_mode_message,
            )
            if permission_mode_message:
                messages.append({"role": "system", "content": permission_mode_message})

            # 执行环境
            execution_mode_message = self._get_or_init_frozen_mode_prompt(
                "frozen_execution_prompt",
                self._build_execution_mode_message,
            )
            if execution_mode_message:
                messages.append({"role": "system", "content": execution_mode_message})

            # 最近对话
            def _build_recent_conversations_prompt() -> str:
                recent_conversations_enabled = (
                    bool(personalization_config.get("recent_conversations_prompt_enabled", False))
                    if isinstance(personalization_config, dict)
                    else False
                )
                if not recent_conversations_enabled:
                    return ""
                try:
                    recent_limit = int(
                        personalization_config.get(
                            "recent_conversations_prompt_limit",
                            RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT,
                        )
                    )
                except Exception:
                    recent_limit = RECENT_CONVERSATIONS_PROMPT_LIMIT_DEFAULT
                recent_limit = max(
                    RECENT_CONVERSATIONS_PROMPT_LIMIT_MIN,
                    min(RECENT_CONVERSATIONS_PROMPT_LIMIT_MAX, recent_limit),
                )
                return self._build_recent_conversations_message(limit=recent_limit) or ""

            recent_conversations_prompt = self._get_or_init_frozen_prompt(
                "frozen_recent_conversations_prompt",
                _build_recent_conversations_prompt,
            )
            if recent_conversations_prompt:
                messages.append({"role": "system", "content": recent_conversations_prompt})

            # 个性化配置
            def _build_personalization_system_prompt() -> str:
                personalization_block = build_personalization_prompt(personalization_config, include_header=False)
                if not personalization_block:
                    return ""
                personalization_template = self.load_prompt("personalization").strip()
                if personalization_template and "{personalization_block}" in personalization_template:
                    return personalization_template.format(personalization_block=personalization_block)
                if personalization_template:
                    return f"{personalization_template}\n{personalization_block}"
                return personalization_block

            personalization_text = self._get_or_init_frozen_prompt(
                "frozen_personalization_prompt",
                _build_personalization_system_prompt,
            )
            if personalization_text:
                messages.append({"role": "system", "content": personalization_text})

            # 工作区信息
            workspace_system = self._get_or_init_frozen_prompt(
                "frozen_workspace_prompt",
                lambda: self.context_manager._build_workspace_system_message(context) or "",
            )
            if workspace_system:
                messages.append({"role": "system", "content": workspace_system})

            # AGENTS.md
            def _build_agents_md_prompt() -> str:
                agents_md_inject_enabled = bool(personalization_config.get("agents_md_auto_inject", False)) if isinstance(personalization_config, dict) else False
                if not agents_md_inject_enabled:
                    return ""
                agents_md_content = self._load_agents_md_content()
                if not agents_md_content:
                    return ""
                agents_md_template = self.load_prompt("agents_md_inject").strip()
                if agents_md_template and "{{AGENTS_MD_CONTENT}}" in agents_md_template:
                    return agents_md_template.replace("{{AGENTS_MD_CONTENT}}", agents_md_content)
                return f"【AGENTS.md 项目规范】\n\n{agents_md_content}\n\n---\n请注意：以上规范来自工作区根目录的 AGENTS.md 文件，若有冲突请以 AGENTS.md 为准。"

            # AGENTS.md 注入开关可能变化，只在开启时生成并缓存，避免关闭时把空字符串冻住
            agents_md_inject_enabled = (
                bool(personalization_config.get("agents_md_auto_inject", False))
                if isinstance(personalization_config, dict)
                else False
            )
            if agents_md_inject_enabled:
                agents_md_text = self._get_or_init_frozen_prompt(
                    "frozen_agents_md_prompt",
                    _build_agents_md_prompt,
                )
                if agents_md_text:
                    messages.append({"role": "system", "content": agents_md_text})

            # skills 列表
            skills_catalog = get_skills_catalog(private_dir=infer_private_skills_dir(self.data_dir))
            enabled_skills = merge_enabled_skills(
                personalization_config.get("enabled_skills") if isinstance(personalization_config, dict) else None,
                skills_catalog,
                personalization_config.get("skills_catalog_snapshot") if isinstance(personalization_config, dict) else None,
            )
            def _build_skills_system_prompt() -> str:
                skills_template = self.load_prompt("skills_system").strip()
                skills_list = build_skills_list(skills_catalog, enabled_skills)
                return build_skills_prompt(skills_template, skills_list) or ""

            skills_prompt = self._get_or_init_frozen_prompt(
                "frozen_skills_prompt",
                _build_skills_system_prompt,
            )
            if skills_prompt:
                messages.append({"role": "system", "content": skills_prompt})

            # 记忆系统（总体长期记忆 + 项目记忆）
            def _build_memory_system_prompt() -> str:
                return self._build_memory_system_content()

            memory_prompt = self._get_or_init_frozen_prompt(
                "frozen_memory_prompt",
                _build_memory_system_prompt,
            )
            if memory_prompt:
                messages.append({"role": "system", "content": memory_prompt})

            # API 自定义 system prompt
            custom_system_prompt = self._get_or_init_frozen_prompt(
                "frozen_custom_system_prompt",
                lambda: (
                    getattr(self.context_manager, "custom_system_prompt", "").strip()
                    if isinstance(getattr(self.context_manager, "custom_system_prompt", None), str)
                    else ""
                ),
            )
            if custom_system_prompt:
                messages.append({"role": "system", "content": custom_system_prompt})

            # 禁用工具提示
            disabled_notice = self._get_or_init_frozen_prompt(
                "frozen_disabled_tools_prompt",
                lambda: self._format_disabled_tool_notice() or "",
            )

            # 多智能体模式额外提示词：可用的子智能体角色（动态 prompt，第一个用户消息后冻结）
            if getattr(self, "multi_agent_mode", False):
                try:
                    available_agents_prompt = self._get_or_init_frozen_prompt(
                        "frozen_available_agents_prompt",
                        lambda: build_available_agents_prompt() or "",
                    )
                    if available_agents_prompt:
                        messages.append({"role": "system", "content": available_agents_prompt})
                except Exception as exc:
                    logger.warning(f"[messages] 注入可用子智能体 prompt 失败: {exc}")
            if disabled_notice:
                messages.append({
                    "role": "system",
                    "content": disabled_notice
                })

            # 添加对话历史（保留完整结构，包括tool_calls和tool消息）
            conversation = context["conversation"]
            replaced_tool_count = 0
            deep_compacted_skipped = 0
            for idx, conv in enumerate(conversation):
                metadata = conv.get("metadata") or {}
                # 深压缩：被标记为 deep_compacted 的消息（整段已压缩前缀）原文保留在历史中用于展示，
                # 但构建请求时整体跳过，不纳入上下文。由于按连续前缀整体标记，
                # 不会出现 assistant.tool_calls 与其 tool 响应被拆散导致的配对悬空。
                if metadata.get("deep_compacted"):
                    deep_compacted_skipped += 1
                    continue
                if conv["role"] == "assistant":
                    # Assistant消息可能包含工具调用
                    message = {
                        "role": conv["role"],
                        "content": conv["content"]
                    }
                    # 对于思考模式（如 DeepSeek thinking），assistant 历史消息中的
                    # reasoning_content 即使为空字符串也需要原样回传，否则下一轮可能被
                    # API 判定为“未回传 reasoning_content”并返回 400。
                    if "reasoning_content" in conv:
                        message["reasoning_content"] = conv.get("reasoning_content", "")
                    # 如果有工具调用信息，添加到消息中
                    tool_calls = conv.get("tool_calls") or []
                    if tool_calls and self._tool_calls_followed_by_tools(conversation, idx, tool_calls):
                        message["tool_calls"] = tool_calls
                    messages.append(message)

                elif conv["role"] == "tool":
                    if shallow_replace_enabled and metadata.get("auto_shallow_compacted"):
                        messages.append({
                            "role": "tool",
                            "content": AUTO_SHALLOW_PLACEHOLDER,
                            "tool_call_id": conv.get("tool_call_id", ""),
                            "name": conv.get("name", "")
                        })
                        replaced_tool_count += 1
                        continue
                    # Tool消息需要保留完整结构
                    images = conv.get("images") or metadata.get("images") or []
                    videos = conv.get("videos") or metadata.get("videos") or []
                    media_refs = conv.get("media_refs") or metadata.get("media_refs") or []
                    content_value = conv.get("content")
                    if isinstance(content_value, list):
                        content_payload = content_value
                    elif images or videos or media_refs:
                        content_payload = self.context_manager._build_content_with_images(
                            content_value,
                            images,
                            videos,
                            media_refs=media_refs,
                        )
                    else:
                        content_payload = content_value
                    message = {
                        "role": "tool",
                        "content": content_payload,
                        "tool_call_id": conv.get("tool_call_id", ""),
                        "name": conv.get("name", "")
                    }
                    messages.append(message)

                else:
                    # User 或普通 System 消息
                    images = conv.get("images") or metadata.get("images") or []
                    videos = conv.get("videos") or metadata.get("videos") or []
                    media_refs = conv.get("media_refs") or metadata.get("media_refs") or []
                    content_payload = (
                        self.context_manager._build_content_with_images(
                            conv["content"],
                            images,
                            videos,
                            media_refs=media_refs,
                        )
                        if (images or videos or media_refs) else conv["content"]
                    )
                    # 调试：记录所有 system 消息
                    if conv["role"] == "system":
                        logger.info(f"[DEBUG build_messages] 添加 system 消息: content前50字={conv['content'][:50]}")
                    # 调试：记录多智能体子智能体输出消息是否进入上下文
                    if metadata.get("multi_agent_message"):
                        logger.info(
                            f"[DEBUG build_messages] 添加多智能体子智能体消息到上下文: "
                            f"role={conv['role']}, subtype={metadata.get('multi_agent_subtype')}, "
                            f"display_name={metadata.get('multi_agent_display_name')}, "
                            f"content前80字={str(conv.get('content', ''))[:80]}"
                        )
                    messages.append({
                        "role": conv["role"],
                        "content": content_payload
                    })

            # 当前用户输入已经在conversation中了，不需要重复添加
            if shallow_replace_enabled:
                print(f"[ContextCompression] build_messages 替换tool占位符: {replaced_tool_count} 条")
            if deep_compacted_skipped:
                print(f"[ContextCompression] build_messages 跳过已深压缩消息: {deep_compacted_skipped} 条")
            return messages
