"""
示例（增强版）

使用 Agent 模式 + Skills

新增功能：
1. 支持命令行随时打断（输入 'p' 暂停，'q' 退出）
2. 暂停后可插入干预消息
3. 支持触发经验总结
4. 查看当前 GoalTree
5. 框架层自动清理不完整的工具调用
6. 支持通过 --trace <ID> 恢复已有 Trace 继续执行
"""

import argparse
import os
import sys
import select
import asyncio
from pathlib import Path

# Clash Verge TUN 模式兼容：禁止 httpx/urllib 自动检测系统 HTTP 代理
# TUN 虚拟网卡已在网络层接管所有流量，不需要应用层再走 HTTP 代理，
# 否则 httpx 检测到 macOS 系统代理 (127.0.0.1:7897) 会导致 ConnectError
os.environ.setdefault("no_proxy", "*")

# 添加项目根目录到 Python 路径
sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from dotenv import load_dotenv
load_dotenv()

from agent.llm.prompts import SimplePrompt
from agent.core.runner import AgentRunner, RunConfig
from agent.core.presets import AgentPreset, register_preset
from agent.trace import (
    FileSystemTraceStore,
    Trace,
    Message,
)
from agent.llm import create_openrouter_llm_call
from agent.tools import get_tool_registry


# ===== 非阻塞 stdin 检测 =====
if sys.platform == 'win32':
    import msvcrt

def check_stdin() -> str | None:
    """
    跨平台非阻塞检查 stdin 输入。
    Windows: 使用 msvcrt.kbhit()
    macOS/Linux: 使用 select.select()
    """
    if sys.platform == 'win32':
        # 检查是否有按键按下
        if msvcrt.kbhit():
            # 读取按下的字符（msvcrt.getwch 是非阻塞读取宽字符）
            ch = msvcrt.getwch().lower()
            if ch == 'p':
                return 'pause'
            if ch == 'q':
                return 'quit'
            # 如果是其他按键，可以选择消耗掉或者忽略
        return None
    else:
        # Unix/Mac 逻辑
        ready, _, _ = select.select([sys.stdin], [], [], 0)
        if ready:
            line = sys.stdin.readline().strip().lower()
            if line in ('p', 'pause'):
                return 'pause'
            if line in ('q', 'quit'):
                return 'quit'
        return None


# ===== 交互菜单 =====

def _read_multiline() -> str:
    """
    读取多行输入，以连续两次回车（空行）结束。

    单次回车只是换行，不会提前终止输入。
    """
    print("\n请输入干预消息（连续输入两次回车结束）：")
    lines: list[str] = []
    blank_count = 0
    while True:
        line = input()
        if line == "":
            blank_count += 1
            if blank_count >= 2:
                break
            lines.append("")          # 保留单个空行
        else:
            blank_count = 0
            lines.append(line)

    # 去掉尾部多余空行
    while lines and lines[-1] == "":
        lines.pop()
    return "\n".join(lines)


async def show_interactive_menu(
    runner: AgentRunner,
    trace_id: str,
    current_sequence: int,
    store: FileSystemTraceStore,
):
    """
    显示交互式菜单，让用户选择操作。

    进入本函数前不再有后台线程占用 stdin，所以 input() 能正常工作。
    """
    print("\n" + "=" * 60)
    print("  执行已暂停")
    print("=" * 60)
    print("请选择操作：")
    print("  1. 插入干预消息并继续")
    print("  2. 触发经验总结（reflect）")
    print("  3. 查看当前 GoalTree")
    print("  4. 手动压缩上下文（compact）")
    print("  5. 继续执行")
    print("  6. 停止执行")
    print("  7. 经验库瘦身（合并相似经验）")
    print("=" * 60)

    while True:
        choice = input("请输入选项 (1-7): ").strip()

        if choice == "1":
            text = _read_multiline()
            if not text:
                print("未输入任何内容，取消操作")
                continue

            print(f"\n将插入干预消息并继续执行...")
            # 从 store 读取实际的 last_sequence，避免本地 current_sequence 过时
            live_trace = await store.get_trace(trace_id)
            actual_sequence = live_trace.last_sequence if live_trace and live_trace.last_sequence else current_sequence
            return {
                "action": "continue",
                "messages": [{"role": "user", "content": text}],
                "after_sequence": actual_sequence,
            }

        elif choice == "2":
            # 触发经验总结
            print("\n触发经验总结...")
            focus = input("请输入反思重点（可选，直接回车跳过）: ").strip()

            # 触发反思
            await perform_reflection(runner, store, trace_id, focus=focus)
            continue

        elif choice == "3":
            goal_tree = await store.get_goal_tree(trace_id)
            if goal_tree and goal_tree.goals:
                print("\n当前 GoalTree:")
                print(goal_tree.to_prompt())
            else:
                print("\n当前没有 Goal")
            continue

        elif choice == "4":
            # 手动压缩上下文
            print("\n正在执行上下文压缩（compact）...")
            try:
                goal_tree = await store.get_goal_tree(trace_id)
                trace = await store.get_trace(trace_id)
                if not trace:
                    print("未找到 Trace，无法压缩")
                    continue

                # 重建当前 history
                main_path = await store.get_main_path_messages(trace_id, trace.head_sequence)
                history = [msg.to_llm_dict() for msg in main_path]
                head_seq = main_path[-1].sequence if main_path else 0
                next_seq = head_seq + 1

                compact_config = RunConfig(trace_id=trace_id)
                new_history, new_head, new_seq = await runner._compress_history(
                    trace_id=trace_id,
                    history=history,
                    goal_tree=goal_tree,
                    config=compact_config,
                    sequence=next_seq,
                    head_seq=head_seq,
                )
                print(f"\n✅ 压缩完成: {len(history)} 条消息 → {len(new_history)} 条")
            except Exception as e:
                print(f"\n❌ 压缩失败: {e}")
            continue

        elif choice == "5":
            print("\n继续执行...")
            return {"action": "continue"}

        elif choice == "6":
            print("\n停止执行...")
            return {"action": "stop"}

        elif choice == "7":
            # 经验库瘦身
            print("\n正在执行经验库瘦身...")
            from agent.tools.builtin.experience import slim_experiences
            try:
                result = await slim_experiences()
                print(f"\n{result}")
            except Exception as e:
                print(f"\n经验库瘦身失败: {e}")
            continue

        else:
            print("无效选项，请重新输入")

async def perform_reflection(runner: AgentRunner, store: FileSystemTraceStore, trace_id: str, focus: str = ""):
    """执行经验总结并保存（带结构化 YAML 解析）"""
    from agent.trace.compaction import build_reflect_prompt
    import re as _re2
    import uuid as _uuid2
    from datetime import datetime
    
    trace = await store.get_trace(trace_id)
    if not trace:
        return
    saved_head = trace.head_sequence

    prompt = build_reflect_prompt()
    if focus:
        prompt += f"\n\n请特别关注：{focus}"

    print("正在生成反思...")
    reflect_cfg = RunConfig(trace_id=trace_id, max_iterations=1, tools=[])
    reflection_text = ""
    
    try:
        result = await runner.run_result(
            messages=[{"role": "user", "content": prompt}],
            config=reflect_cfg,
        )
        reflection_text = result.get("summary", "")
    finally:
        # 恢复 head_sequence（反思消息成为侧枝，不污染主对话）
        await store.update_trace(trace_id, head_sequence=saved_head)

    # 追加到 experiences 文件
    if reflection_text:
        # experiences_path = runner.experiences_path  # 已废弃，使用知识系统 or "./.cache/experiences_restore.md"
        os.makedirs(os.path.dirname(experiences_path), exist_ok=True)

        pattern = r"-\s*\[(?P<tags>.*?)\]\s*(?P<content>.*)"
        matches = list(_re2.finditer(pattern, reflection_text))

        structured_entries = []
        for match in matches:
            tags_str = match.group("tags")
            content = match.group("content")

            intent_match = _re2.search(r"intent:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
            state_match = _re2.search(r"state:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)

            intents = [i.strip() for i in intent_match.group(1).split(",")] if intent_match and intent_match.group(1) else []
            states = [s.strip() for s in state_match.group(1).split(",")] if state_match and state_match.group(1) else []

            ex_id = f"ex_{datetime.now().strftime('%m%d%H%M')}_{_uuid2.uuid4().hex[:4]}"
            
            entry = f"---\nid: {ex_id}\ntrace_id: {trace_id}\ntags: {{intent: {intents}, state: {states}}}\nmetrics: {{helpful: 1, harmful: 0}}\ncreated_at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n---\n- {content}\n- 经验ID: [{ex_id}]"
            structured_entries.append(entry)

        if structured_entries:
            final_output = "\n\n" + "\n\n".join(structured_entries)
            with open(experiences_path, "a", encoding="utf-8") as f:
                f.write(final_output)
            print(f"\n✅ 提取了 {len(structured_entries)} 条经验，已结构化并保存到: {experiences_path}")
            print("\n--- 反思内容（结构化后） ---")
            print(final_output.strip())
            print("--- 结束 ---\n")
        else:
            print("\n⚠️ 未能解析出符合格式的经验条目，已保存原始纯文本以供检查。")
            header = f"\n\n---\n\n## [Raw] {trace_id} ({datetime.now().strftime('%Y-%m-%d %H:%M')})\n\n"
            with open(experiences_path, "a", encoding="utf-8") as f:
                f.write(header + reflection_text + "\n")
            print(reflection_text)
    else:
        print("未生成反思内容")

async def main():
    # 解析命令行参数
    parser = argparse.ArgumentParser(description="任务 (Agent 模式 + 交互增强)")
    parser.add_argument(
        "--trace", type=str, default=None,
        help="已有的 Trace ID，用于恢复继续执行（不指定则新建）",
    )
    args = parser.parse_args()

    # 路径配置
    base_dir = Path(__file__).parent
    project_root = base_dir.parent.parent
    prompt_path = base_dir / "production.prompt"
    output_dir = base_dir / "output_1"
    output_dir.mkdir(exist_ok=True)

    # 加载项目级 presets（examples/restore/presets.json）
    presets_path = base_dir / "presets.json"
    if presets_path.exists():
        import json
        with open(presets_path, "r", encoding="utf-8") as f:
            project_presets = json.load(f)
        for name, cfg in project_presets.items():
            register_preset(name, AgentPreset(**cfg))
        print(f"   - 已加载项目 presets: {list(project_presets.keys())}")

    # Skills 目录（可选：用户自定义 skills）
    # 注意：内置 skills（agent/memory/skills/）会自动加载
    skills_dir = str(base_dir / "skills")

    print("=" * 60)
    print("mcp/skills 发现、获取、评价 分析任务 (Agent 模式 + 交互增强)")
    print("=" * 60)
    print()
    print("💡 交互提示：")
    print("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
    print("   - 执行过程中输入 'q' 或 'quit' 停止执行")
    print("=" * 60)
    print()

    # 1. 加载 prompt
    print("1. 加载 prompt 配置...")
    prompt = SimplePrompt(prompt_path)

    # 2. 构建消息（仅新建时使用，恢复时消息已在 trace 中）
    print("2. 构建任务消息...")
    messages = prompt.build_messages()

    # 3. 创建 Agent Runner（配置 skills）
    print("3. 创建 Agent Runner...")
    print(f"   - Skills 目录: {skills_dir}")
    print(f"   - 模型: {prompt.config.get('model', 'sonnet-4.5')}")

    # 加载自定义工具
    print("   - 加载自定义工具: nanobanana")
    import examples.how.tool  # 导入自定义工具模块，触发 @tool 装饰器注册

    store = FileSystemTraceStore(base_path=".trace")
    runner = AgentRunner(
        trace_store=store,
        llm_call=create_openrouter_llm_call(model=f"anthropic/claude-{prompt.config.get('model', 'sonnet-4.5')}"),
        skills_dir=skills_dir,
        debug=True
    )

    # 4. 判断是新建还是恢复
    resume_trace_id = args.trace
    if resume_trace_id:
        # 验证 trace 存在
        existing_trace = await store.get_trace(resume_trace_id)
        if not existing_trace:
            print(f"\n错误: Trace 不存在: {resume_trace_id}")
            sys.exit(1)
        print(f"4. 恢复已有 Trace: {resume_trace_id[:8]}...")
        print(f"   - 状态: {existing_trace.status}")
        print(f"   - 消息数: {existing_trace.total_messages}")
        print(f"   - 任务: {existing_trace.task}")
    else:
        print(f"4. 启动新 Agent 模式...")

    print()

    final_response = ""
    current_trace_id = resume_trace_id
    current_sequence = 0
    should_exit = False

    try:
        # 恢复模式：不发送初始消息，只指定 trace_id 续跑
        if resume_trace_id:
            initial_messages = None  # None = 未设置，触发早期菜单检查
            config = RunConfig(
                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
                temperature=float(prompt.config.get('temperature', 0.3)),
                max_iterations=1000,
                trace_id=resume_trace_id,
            )
        else:
            initial_messages = messages
            config = RunConfig(
                model=f"claude-{prompt.config.get('model', 'sonnet-4.5')}",
                temperature=float(prompt.config.get('temperature', 0.3)),
                max_iterations=1000,
                name="社交媒体内容解构、建构、评估任务",
            )

        while not should_exit:
            # 如果是续跑，需要指定 trace_id
            if current_trace_id:
                config.trace_id = current_trace_id

            # 清理上一轮的响应，避免失败后显示旧内容
            final_response = ""

            # 如果 trace 已完成/失败且没有新消息，直接进入交互菜单
            # 注意：initial_messages 为 None 表示未设置（首次加载），[] 表示有意为空（用户选择"继续"）
            if current_trace_id and initial_messages is None:
                check_trace = await store.get_trace(current_trace_id)
                if check_trace and check_trace.status in ("completed", "failed"):
                    if check_trace.status == "completed":
                        print(f"\n[Trace] ✅ 已完成")
                        print(f"  - Total messages: {check_trace.total_messages}")
                        print(f"  - Total cost: ${check_trace.total_cost:.4f}")
                    else:
                        print(f"\n[Trace] ❌ 已失败: {check_trace.error_message}")
                    current_sequence = check_trace.head_sequence

                    menu_result = await show_interactive_menu(
                        runner, current_trace_id, current_sequence, store
                    )

                    if menu_result["action"] == "stop":
                        break
                    elif menu_result["action"] == "continue":
                        new_messages = menu_result.get("messages", [])
                        if new_messages:
                            initial_messages = new_messages
                            config.after_sequence = menu_result.get("after_sequence")
                        else:
                            # 无新消息：对 failed trace 意味着重试，对 completed 意味着继续
                            initial_messages = []
                            config.after_sequence = None
                        continue
                    break

                # 对 stopped/running 等非终态的 trace，直接续跑
                initial_messages = []

            print(f"{'▶️ 开始执行...' if not current_trace_id else '▶️ 继续执行...'}")

            # 执行 Agent
            paused = False
            try:
                async for item in runner.run(messages=initial_messages, config=config):
                    # 检查用户中断
                    cmd = check_stdin()
                    if cmd == 'pause':
                        # 暂停执行
                        print("\n⏸️ 正在暂停执行...")
                        if current_trace_id:
                            await runner.stop(current_trace_id)

                        # 等待一小段时间让 runner 处理 stop 信号
                        await asyncio.sleep(0.5)

                        # 显示交互菜单
                        menu_result = await show_interactive_menu(
                            runner, current_trace_id, current_sequence, store
                        )

                        if menu_result["action"] == "stop":
                            should_exit = True
                            paused = True
                            break
                        elif menu_result["action"] == "continue":
                            # 检查是否有新消息需要插入
                            new_messages = menu_result.get("messages", [])
                            if new_messages:
                                # 有干预消息，需要重新启动循环
                                initial_messages = new_messages
                                after_seq = menu_result.get("after_sequence")
                                if after_seq is not None:
                                    config.after_sequence = after_seq
                                paused = True
                                break
                            else:
                                # 没有新消息，需要重启执行
                                initial_messages = []
                                config.after_sequence = None
                                paused = True
                                break

                    elif cmd == 'quit':
                        print("\n🛑 用户请求停止...")
                        if current_trace_id:
                            await runner.stop(current_trace_id)
                        should_exit = True
                        break

                    # 处理 Trace 对象（整体状态变化）
                    if isinstance(item, Trace):
                        current_trace_id = item.trace_id
                        if item.status == "running":
                            print(f"[Trace] 开始: {item.trace_id[:8]}...")
                        elif item.status == "completed":
                            print(f"\n[Trace] ✅ 完成")
                            print(f"  - Total messages: {item.total_messages}")
                            print(f"  - Total tokens: {item.total_tokens}")
                            print(f"  - Total cost: ${item.total_cost:.4f}")
                        elif item.status == "failed":
                            print(f"\n[Trace] ❌ 失败: {item.error_message}")
                        elif item.status == "stopped":
                            print(f"\n[Trace] ⏸️ 已停止")

                    # 处理 Message 对象（执行过程）
                    elif isinstance(item, Message):
                        current_sequence = item.sequence

                        if item.role == "assistant":
                            content = item.content
                            if isinstance(content, dict):
                                text = content.get("text", "")
                                tool_calls = content.get("tool_calls")

                                if text and not tool_calls:
                                    # 纯文本回复（最终响应）
                                    final_response = text
                                    print(f"\n[Response] Agent 回复：")
                                    print(text)
                                elif text:
                                    preview = text[:150] + "..." if len(text) > 150 else text
                                    print(f"[Assistant] {preview}")

                                if tool_calls:
                                    for tc in tool_calls:
                                        tool_name = tc.get("function", {}).get("name", "unknown")
                                        print(f"[Tool Call] 🛠️  {tool_name}")

                        elif item.role == "tool":
                            content = item.content
                            if isinstance(content, dict):
                                tool_name = content.get("tool_name", "unknown")
                                print(f"[Tool Result] ✅ {tool_name}")
                            if item.description:
                                desc = item.description[:80] if len(item.description) > 80 else item.description
                                print(f"  {desc}...")

            except Exception as e:
                print(f"\n执行出错: {e}")
                import traceback
                traceback.print_exc()

            # paused → 菜单已在暂停时内联显示过
            if paused:
                if should_exit:
                    break
                continue

            # quit → 直接退出
            if should_exit:
                break

            # Runner 退出（完成/失败/停止/异常）→ 显示交互菜单
            if current_trace_id:
                # 🌟 新增：自动触发反思的生命周期钩子
                check_trace = await store.get_trace(current_trace_id)
                if check_trace and check_trace.status in ("completed", "failed"):
                    print(f"\n⚙️ 任务已结束 (状态: {check_trace.status})，正在自动触发经验总结...")
                    
                    # 如果是失败状态，自动带上针对性的 focus 提示
                    auto_focus = "本次任务执行失败了，请重点反思失败的原因、踩坑点以及未来应如何避免。" if check_trace.status == "failed" else ""
                    
                    await perform_reflection(runner, store, current_trace_id, focus=auto_focus)

                # 自动反思结束后，依然弹出菜单，让用户决定是彻底退出(6)还是查看总结(3)
                menu_result = await show_interactive_menu(
                    runner, current_trace_id, current_sequence, store
                )

                if menu_result["action"] == "stop":
                    break
                elif menu_result["action"] == "continue":
                    new_messages = menu_result.get("messages", [])
                    if new_messages:
                        initial_messages = new_messages
                        config.after_sequence = menu_result.get("after_sequence")
                    else:
                        initial_messages = []
                        config.after_sequence = None
                    continue
            break

    except KeyboardInterrupt:
        print("\n\n用户中断 (Ctrl+C)")
        if current_trace_id:
            await runner.stop(current_trace_id)

    # 6. 输出结果
    if final_response:
        print()
        print("=" * 60)
        print("Agent 响应:")
        print("=" * 60)
        print(final_response)
        print("=" * 60)
        print()

        # 7. 保存结果
        output_file = output_dir / "result.txt"
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write(final_response)

        print(f"✓ 结果已保存到: {output_file}")
        print()

    # 可视化提示
    if current_trace_id:
        print("=" * 60)
        print("可视化 Step Tree:")
        print("=" * 60)
        print("1. 启动 API Server:")
        print("   python3 api_server.py")
        print()
        print("2. 浏览器访问:")
        print("   http://43.106.118.91:8000/api/traces")
        print()
        print(f"3. Trace ID: {current_trace_id}")
        print("=" * 60)


if __name__ == "__main__":
    asyncio.run(main())
