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.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 ModeMixin:
    """MainTerminalContextMixin mode 能力 mixin。"""

    # 权限模式说明（代码中存储的简要版本）
    _PERMISSION_MODE_DESC = {
        "unrestricted": "当前处于无限制模式，所有工具均可直接使用，无需额外批准。",
        "readonly": "当前处于只读模式，仅能执行不会修改工作区的读取类操作。",
        "approval": "当前处于批准模式，修改工作区的操作需要用户批准后方可执行。",
        "auto_approval": "当前处于自动审核模式，工作区内文件编辑可直接执行，高风险操作由后台审批智能体自动审核。",
    }

    _PERMISSION_MODE_LABEL = {
        "unrestricted": "无限制",
        "readonly": "只读",
        "approval": "批准",
        "auto_approval": "自动审核",
    }

    _EXECUTION_MODE_LABEL = {
        "sandbox": "沙箱",
        "direct": "完全访问权限",
    }

    def _build_permission_mode_message(self) -> Optional[str]:
        """根据当前权限模式构建权限说明消息（从模板读取并替换占位符）"""
        template = self.load_prompt("permission_mode")
        if not template:
            return None

        mode = self.get_permission_mode()

        # 根据模式生成详细规则
        if mode == "unrestricted":
            detailed_rules = (
                "- 所有命令和工具均可直接使用，无需用户批准\n"
                "- run_command：支持任意命令，包括管道、重定向、子shell等\n"
                "- 文件操作：read_file / write_file / edit_file 可直接使用；其他文件管理请通过 run_command 执行；需要 Python 时先探测并选择合适解释器"
            )
        elif mode == "readonly":
            detailed_rules = (
                "- run_command：统一在系统只读沙箱中执行\n"
                "- 若命令触发写入，系统会直接拒绝并返回权限错误\n"
                "- 文件操作：仅允许 read_file、view_image、view_video 等读取类工具\n"
                "- 禁止：write_file、edit_file 及会修改工作区的命令操作"
            )
        elif mode == "approval":
            detailed_rules = (
                "- run_command：先在系统只读沙箱执行；仅当出现权限拒绝时才触发审批\n"
                "- 审批通过后：仅当前这一次命令会重试为可写沙箱执行\n"
                "- 需要用户批准：terminal_input、write_file、edit_file、save_webpage 及其他会修改工作区的操作\n"
                "- 被拒绝或超时：本次操作不会执行写入"
            )
        elif mode == "auto_approval":
            detailed_rules = (
                "- write_file / edit_file：若路径在当前工作区内，直接执行；工作区外路径会进入审批流程\n"
                "- run_command：先在系统只读沙箱执行；仅当出现权限拒绝时才触发自动审批\n"
                "- 自动审批由后台审批智能体执行，默认只判断危险性与越权风险，不判断任务必要性\n"
                "- 自动审批拒绝：本次工具调用会返回“拒绝+理由”，主循环继续；人工拒绝可随时接管\n"
                "- 可随时人工接管：用户在审批面板点击同意/拒绝/切换无限制后，自动审批会立即停止并以人工决策为准"
            )
        else:
            detailed_rules = ""

        return template.format(
            permission_mode=mode,
            permission_mode_label=self._PERMISSION_MODE_LABEL.get(mode, mode),
            mode_description=self._PERMISSION_MODE_DESC.get(mode, ""),
            detailed_rules=detailed_rules,
        )

    def _build_execution_mode_message(self) -> Optional[str]:
        """根据当前执行环境模式构建提示消息。"""
        # 仅宿主机模式注入；Docker 模式不需要该提示。
        try:
            if not getattr(self, "_is_host_mode", lambda: False)():
                return None
        except Exception:
            return None
        template = self.load_prompt("execution_mode")
        if not template:
            return None
        state = {}
        if hasattr(self, "get_execution_mode_state"):
            try:
                state = self.get_execution_mode_state() or {}
            except Exception:
                state = {}
        mode = str(state.get("mode") or "sandbox").strip().lower()
        mode_label = self._EXECUTION_MODE_LABEL.get(mode, mode)
        if mode == "sandbox":
            rules = (
                "- 所有命令默认在系统 OS 沙箱中执行\n"
                "- 若操作受系统权限限制：先提供 1 个安全替代方案并执行；若仍无法满足目标，明确请求用户切换执行环境为“完全访问权限”后重试\n"
                "- 不要通过复杂绕过手段反复尝试规避沙箱限制；若任务本质需要更高权限，应直接说明并等待用户确认"
            )
            net = getattr(self, "host_network_permission", "restricted")
            if net == "restricted":
                network_rules = "- 网络：受限（仅允许 localhost，外部网络不可达）"
            elif net == "full":
                network_rules = "- 网络：完全开放"
            else:
                network_rules = ""
        else:
            rules = (
                "- 当前为宿主机直接执行模式（完全访问权限）\n"
                "- 仅在必须时执行高权限操作，保持最小化命令范围\n"
                "- 涉及删除/覆盖/系统级变更前，先说明风险再执行"
            )
            network_rules = ""
        return template.format(
            execution_mode=mode,
            execution_mode_label=mode_label,
            rules=rules,
            network_rules=network_rules,
        )

    def _get_or_init_frozen_mode_prompt(self, key: str, builder) -> Optional[str]:
            return self._get_or_init_frozen_prompt(key, builder)

    def _get_or_init_frozen_prompt(self, key: str, builder) -> Optional[str]:
            cm = getattr(self, "context_manager", None)
            meta = getattr(cm, "conversation_metadata", {}) if cm else {}
            cached = meta.get(key) if isinstance(meta, dict) else None
            if isinstance(cached, str) and cached:
                return cached

            built = builder() or ""
            conv_id = getattr(cm, "current_conversation_id", None) if cm else None
            if cm and conv_id:
                try:
                    target_manager = (
                        cm._get_conversation_manager_for_id(conv_id)
                        if hasattr(cm, "_get_conversation_manager_for_id")
                        else cm.conversation_manager
                    )
                    target_manager.update_conversation_metadata(conv_id, {key: built})
                    if isinstance(cm.conversation_metadata, dict):
                        cm.conversation_metadata[key] = built
                except Exception:
                    pass
            return built
