"""
prompt_manager.py - Planner Prompt 构造器

该模块负责构造适合 LLM Planner 的 Prompt，使 LLM 能够生成标准化的 Plan JSON。

职责边界：
    - 构造高质量 Prompt
    - 管理 system instruction
    - 不调用 LLM
    - 不执行 capability

设计原则：
    - Prompt 必须清晰、无歧义
    - 必须明确告诉 LLM 角色和约束
    - 必须提供完整的输出格式规范
    - 必须考虑安全边界

使用方式：
    from prompt_manager import PlannerPromptManager
    
    manager = PlannerPromptManager()
    prompt = manager.build_plan_prompt(
        user_intent="降温",
        world_snapshot={"temperature": 32},
        available_tools=["adjust_fan", "speak"],
        domain_rules={"high_risk_actions": ["turn_off"]},
    )
"""

from __future__ import annotations

import json
from typing import Any


# =============================================================================
# Prompt 模板常量
# =============================================================================

# Planner System Instruction
PLANNER_SYSTEM_INSTRUCTION = """你是一个专业的 AI Agent Planner。

你的职责是根据用户意图和当前世界状态，生成标准化的行动计划（Plan）。

## 核心约束

1. 只生成 Plan，不执行动作
   - 你只输出 Plan JSON，不调用任何能力
   - 实际执行由 Executor 负责

2. 只能使用给定的能力
   - 必须从 available_tools 列表中选择
   - 禁止虚构或使用未提供的能力

3. 必须输出严格 JSON
   - 输出必须是一个可被 Plan.from_dict() 和 PlanStep.from_dict() 解析的标准 JSON 对象
   - 禁止输出 markdown
   - 禁止输出解释文字
   - 禁止输出额外前后缀
   - 禁止缺失必需字段
   - 禁止使用未定义字段替代标准字段

4. 安全优先原则
   - 高风险动作必须设置 requires_confirmation=true
   - 不确定时优先 ask_user 或 speak
   - 禁止直接执行危险动作

5. 世界状态冲突处理
   - 当世界状态显示可能存在冲突时
   - 优先询问用户确认（ask_user）
   - 不要自动执行可能重复或有风险的动作

## 工具类型说明

- execute: 执行具体动作
- ask_user: 询问用户确认
- speak: 语音/文本输出
- query_world: 查询世界状态
- query_knowledge: 查询知识库
- noop: 空操作

## 风险等级

- low: 低风险，可自动执行
- medium: 中风险，建议确认
- high: 高风险，必须确认

## 输出格式要求

输出 JSON 必须包含以下字段：

Plan 顶层字段：
- plan_id: string (自动生成，唯一标识)
- goal: string (计划目标描述)
- reasoning: string (推理过程说明)
- risk_level: "low" | "medium" | "high"
- requires_confirmation: true | false
- confirmation_message: string | null
- steps: array (步骤数组)
- status: "created" | "pending" | "executing" | "completed" | "failed"
- source: string (计划来源，如 "llm"）
- metadata: object (元数据)

每个 step 必须包含以下字段：
- step_id: number (步骤序号，从 1 开始)
- action: string (能力名称)
- tool_call_type: "execute" | "ask_user" | "speak" | "query_world" | "query_knowledge" | "noop"
- parameters: object (执行参数)
- preconditions: object (前置条件)
- fallback: null | object (失败回退)
- status: "pending" | "executing" | "completed" | "failed" | "skipped"
- description: string (步骤描述)
- requires_confirmation: true | false
- confirmation_message: string | null
- metadata: object (步骤元数据)

重要：直接输出 JSON 对象，不要包裹在任何代码块或前后缀中。"""
"""

# 简单场景 Prompt 模板
SIMPLE_SCENARIO_TEMPLATE = """## 用户意图
{user_intent}

## 当前世界状态
{world_snapshot}

## 可用能力
{available_tools}

{domain_rules_section}

{recent_context_section}

请根据以上信息，生成一个标准 Plan JSON。"""


# 无可用能力时的 Prompt
NO_TOOLS_TEMPLATE = """## 用户意图
{user_intent}

## 当前世界状态
{world_snapshot}

## 警告：没有可用能力

你的系统目前没有任何可用能力。
请生成一个 SPEAK plan，告知用户当前无法满足其请求。

请生成一个标准 Plan JSON。"""


# 意图不明确时的 Prompt
UNCLEAR_INTENT_TEMPLATE = """## 用户意图
{user_intent}

## 当前世界状态
{world_snapshot}

## 可用能力
{available_tools}

## 警告：用户意图不明确

用户请求不够明确，系统无法确定具体行动。
请生成一个 ASK_USER plan，询问用户澄清其意图。

请生成一个标准 Plan JSON。"""


# =============================================================================
# Planner Prompt 管理器
# =============================================================================

class PlannerPromptManager:
    """Planner Prompt 管理器
    
    负责构造适合 LLM Planner 的 Prompt。
    
    属性：
        system_instruction: 系统指令
        include_reasoning: 是否在输出中包含推理过程
    
    示例：
        >>> manager = PlannerPromptManager()
        >>> prompt = manager.build_plan_prompt(
        ...     user_intent="降温",
        ...     world_snapshot={"temperature": 32},
        ...     available_tools=["adjust_fan"],
        ... )
    """
    
    def __init__(
        self,
        system_instruction: str | None = None,
        include_reasoning: bool = True,
    ):
        """初始化 Prompt 管理器
        
        Args:
            system_instruction: 自定义系统指令，None 则使用默认指令
            include_reasoning: 是否包含推理过程
        """
        self.system_instruction = system_instruction or PLANNER_SYSTEM_INSTRUCTION
        self.include_reasoning = include_reasoning
    
    def build_plan_prompt(
        self,
        user_intent: str,
        world_snapshot: dict[str, Any],
        available_tools: list[str],
        domain_rules: dict[str, Any] | None = None,
        recent_context: dict[str, Any] | None = None,
    ) -> str:
        """构建 Plan Prompt
        
        主要入口方法。根据输入构建完整的 prompt。

        设计原则：
        - 专注于 prompt 构造，不做复杂的业务判断
        - 复杂场景判断由 planner.py 负责
        - 只保留必要的轻量分支：
          1. 无可用工具
          2. 意图明显不清晰
          3. 默认普通 prompt

        Args:
            user_intent: 用户意图
            world_snapshot: 世界状态快照
            available_tools: 可用能力列表
            domain_rules: 领域规则
            recent_context: 最近上下文（传递给 LLM 参考）
        
        Returns:
            完整的 prompt 字符串
        """
        domain_rules = domain_rules or {}
        recent_context = recent_context or {}

        # 轻量判断分支
        if not available_tools:
            scenario_prompt = self._build_no_tools_prompt(user_intent, world_snapshot)
        elif self._is_unclear_intent_simple(user_intent):
            scenario_prompt = self._build_unclear_intent_prompt(
                user_intent, world_snapshot, available_tools
            )
        else:
            scenario_prompt = self._build_default_prompt(
                user_intent, world_snapshot, available_tools, domain_rules, recent_context
            )

        # 组合完整 prompt
        return f"{self.system_instruction}\n\n{scenario_prompt}"

    def _is_unclear_intent_simple(self, user_intent: str) -> bool:
        """简单判断意图是否不清晰
        
        轻量方法，只做最基础的检查。
        复杂判断由 planner.py 负责。
        
        Args:
            user_intent: 用户意图
        
        Returns:
            意图是否不清晰
        """
        # 意图过短
        if len(user_intent.strip()) < 2:
            return True
        return False
    
    def build_system_instruction(self) -> str:
        """获取系统指令
        
        Returns:
            系统指令字符串
        """
        return self.system_instruction

    def _build_default_prompt(
        self,
        user_intent: str,
        world_snapshot: dict[str, Any],
        available_tools: list[str],
        domain_rules: dict[str, Any],
        recent_context: dict[str, Any],
    ) -> str:
        """构建默认 Prompt
        
        统一处理大多数场景的 prompt 构造。
        
        Args:
            user_intent: 用户意图
            world_snapshot: 世界状态
            available_tools: 可用能力
            domain_rules: 领域规则
            recent_context: 最近上下文
        
        Returns:
            场景 prompt 字符串
        """
        # 格式化世界状态
        if isinstance(world_snapshot, dict):
            world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
        else:
            world_str = str(world_snapshot)

        # 格式化可用工具
        tools_str = ", ".join(f'"{t}"' for t in available_tools)

        # 格式化领域规则
        if domain_rules:
            rules_str = f"\n\n## 领域规则\n{json.dumps(domain_rules, ensure_ascii=False, indent=2)}"
        else:
            rules_str = ""

        # 格式化最近上下文
        if recent_context:
            context_str = f"\n\n## 历史上下文\n{json.dumps(recent_context, ensure_ascii=False, indent=2)}"
        else:
            context_str = ""

        return SIMPLE_SCENARIO_TEMPLATE.format(
            user_intent=user_intent,
            world_snapshot=world_str,
            available_tools=tools_str,
            domain_rules_section=rules_str,
            recent_context_section=context_str,
        )
    
    def _build_no_tools_prompt(
        self,
        user_intent: str,
        world_snapshot: dict[str, Any],
    ) -> str:
        """构建无可用工具时的 Prompt"""
        if isinstance(world_snapshot, dict):
            world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
        else:
            world_str = str(world_snapshot)
        
        return NO_TOOLS_TEMPLATE.format(
            user_intent=user_intent,
            world_snapshot=world_str,
        )
    
    def _build_unclear_intent_prompt(
        self,
        user_intent: str,
        world_snapshot: dict[str, Any],
        available_tools: list[str],
    ) -> str:
        """构建意图不明确时的 Prompt"""
        if isinstance(world_snapshot, dict):
            world_str = json.dumps(world_snapshot, ensure_ascii=False, indent=2)
        else:
            world_str = str(world_snapshot)
        
        tools_str = ", ".join(f'"{t}"' for t in available_tools)
        
        return UNCLEAR_INTENT_TEMPLATE.format(
            user_intent=user_intent,
            world_snapshot=world_str,
            available_tools=tools_str,
        )


# =============================================================================
# 主程序入口（测试示例）
# =============================================================================

if __name__ == "__main__":
    print("=" * 70)
    print("Planner Prompt Manager 测试")
    print("=" * 70)
    
    manager = PlannerPromptManager()
    
    # 测试 1：简单场景
    print("\n[测试 1] 简单场景 - 降温")
    print("-" * 40)
    prompt = manager.build_plan_prompt(
        user_intent="打开风扇降温",
        world_snapshot={"temperature": 32, "humidity": 70},
        available_tools=["adjust_fan", "speak", "query"],
        domain_rules={"high_risk_actions": ["turn_off"]},
    )
    print(f"Prompt 长度: {len(prompt)} 字符")
    print(f"包含 system instruction: {'AI Agent Planner' in prompt}")
    print(f"包含 JSON 格式: {'plan_id' in prompt}")
    
    # 测试 2：意图不明确
    print("\n[测试 2] 意图不明确")
    print("-" * 40)
    prompt = manager.build_plan_prompt(
        user_intent="那个事情",
        world_snapshot={"status": "normal"},
        available_tools=["query", "speak"],
    )
    print(f"Prompt 长度: {len(prompt)} 字符")
    print(f"包含 ASK_USER 提示: {'询问用户' in prompt or 'ask_user' in prompt.lower()}")
    
    # 测试 3：无可用工具
    print("\n[测试 3] 无可用工具")
    print("-" * 40)
    prompt = manager.build_plan_prompt(
        user_intent="打开空调",
        world_snapshot={"temperature": 30},
        available_tools=[],
    )
    print(f"Prompt 长度: {len(prompt)} 字符")
    print(f"包含无可用能力提示: {'没有可用能力' in prompt}")
    
    # 测试 4：正常意图（含历史上下文）
    print("\n[测试 4] 正常意图")
    print("-" * 40)
    prompt = manager.build_plan_prompt(
        user_intent="先降温然后喂食",
        world_snapshot={"temperature": 32, "hunger_level": 80},
        available_tools=["adjust_fan", "feed", "speak"],
        domain_rules={"high_risk_actions": ["turn_off"], "medium_risk_actions": ["adjust"]},
        recent_context={"recent_actions": ["adjust_fan"]},
    )
    print(f"Prompt 长度: {len(prompt)} 字符")
    print(f"包含历史上下文: {'历史上下文' in prompt}")

    # 测试 5：打印完整 prompt 示例
    print("\n[测试 5] 完整 Prompt 示例")
    print("-" * 40)
    prompt = manager.build_plan_prompt(
        user_intent="打开风扇降温",
        world_snapshot={"temperature": 32, "humidity": 70},
        available_tools=["adjust_fan", "speak"],
        domain_rules={"high_risk_actions": ["turn_off"]},
    )
    # 只打印前 500 字符
    print(prompt[:500])
    print("...")

    # 测试 6：验证 system instruction 中没有 markdown 代码块模板
    print("\n[测试 6] 验证 JSON 输出约束")
    print("-" * 40)
    system_instr = manager.build_system_instruction()
    print(f"包含 markdown 代码块: {'```' in system_instr}")
    print(f"包含禁止输出 markdown: {'禁止输出 markdown' in system_instr}")
    print(f"包含禁止解释文字: {'禁止输出解释文字' in system_instr}")

    print("\n" + "=" * 70)
    print("Planner Prompt Manager 测试完成")
    print("=" * 70)

