"""
🧪 Phase 1 通关 · 5 步拆解
每步补完代码 → 跑通 → 找我批 → 下一步
最终目标：不看答案写出完整单步 Agent
"""
import json
import os
from openai import OpenAI

# ═══════════════════════════════════════════════════════════
# 初始化（所有练习共用，不用改）
# ═══════════════════════════════════════════════════════════
client = OpenAI(
    api_key=os.getenv("DEEPSEEK_API_KEY"),
    base_url="https://api.deepseek.com",
)

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "执行数学计算。当用户问算术问题时使用。",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {"type": "string", "description": "数学表达式，如 '3*15+7'"}
                },
                "required": ["expression"],
            },
        },
    }
]


def calculate(expression: str) -> float:
    allowed = set("0123456789+-*/().%^ ")
    cleaned = "".join(ch for ch in expression if ch in allowed)
    return eval(cleaned)


TOOL_MAP = {"calculate": calculate}


# ═══════════════════════════════════════════════════════════
# 练习 1：造消息
# 任务：补完函数体，让它返回 OpenAI 标准格式的消息列表
# ═══════════════════════════════════════════════════════════

def build_messages(user_input: str) -> list[dict]:
    """接收用户输入，返回 [system消息, user消息]"""
    # 👇 在下面写你的代码（2 行）
    pass


# ── 测试 ──
def test1():
    msgs = build_messages("你好")
    assert len(msgs) == 2, f"应该有 2 条消息，实际 {len(msgs)}"
    assert msgs[0]["role"] == "system"
    assert msgs[1]["role"] == "user"
    assert msgs[1]["content"] == "你好"
    print("✅ 练习 1 通过！")


# ═══════════════════════════════════════════════════════════
# 练习 2：调 LLM
# 任务：用 build_messages() 造消息，发给 LLM，打印返回内容
# ═══════════════════════════════════════════════════════════

def call_llm(user_input: str) -> str:
    """造消息 → 发给 LLM → 返回 content"""
    # 👇 在下面写你的代码（3-5 行）
    pass


# ── 测试 ──
def test2():
    answer = call_llm("你好，请用一句话介绍自己")
    assert answer is not None
    assert len(answer) > 0
    print(f"✅ 练习 2 通过！LLM 回复：{answer[:50]}...")


# ═══════════════════════════════════════════════════════════
# 练习 3：读返回，判断要不要调工具
# 任务：调用 LLM（带 TOOLS），然后判断 finish_reason
# ═══════════════════════════════════════════════════════════

def think(user_input: str):
    """发给 LLM（带 TOOLS），判断要不要调工具"""
    # 👇 在下面写你的代码（5-8 行）
    # 1. 用 build_messages 造消息
    # 2. 发给 LLM（要传 tools=TOOLS）
    # 3. 如果 finish_reason == "stop" 且有 content → 打印 "直接回答：..."
    # 4. 否则 → 打印 "需要调工具！" 和 tool_calls 内容
    pass


# ── 测试 ──
def test3():
    print("--- 测试 1：不需要工具 ---")
    think("你好")
    # 预期输出：直接回答：你好！...
    print("\n--- 测试 2：需要工具 ---")
    think("15乘以37等于多少")
    # 预期输出：需要调工具！LLM 要求调 calculate...


# ═══════════════════════════════════════════════════════════
# 练习 4：执行工具，结果回传
# 任务：LLM 要求调工具 → 执行 → 结果塞回 messages
# ═══════════════════════════════════════════════════════════

def act(user_input: str):
    """如果需要工具就执行，把结果塞进 messages"""
    # 👇 在下面写你的代码（8-12 行）
    # 1. 用 build_messages 造消息
    # 2. 发给 LLM（带 TOOLS）
    # 3. 如果不需要工具 → 直接返回
    # 4. 如果需要 → for tc in tool_calls → 用 TOOL_MAP 执行 → append 结果
    # 5. 打印 messages 的长度
    pass


# ── 测试 ──
def test4():
    print("--- 测试：15乘以37 ---")
    act("15乘以37等于多少")
    # 预期输出：工具执行完毕，messages 现在有 X 条


# ═══════════════════════════════════════════════════════════
# 练习 5：第二轮 LLM 调用 → 生成最终答案
# 任务：工具执行完，再调一次 LLM，让它读结果并回答
# ═══════════════════════════════════════════════════════════

def run_agent(user_input: str) -> str:
    """完整的单步 Agent：Think → Act → Observe → 回答"""
    # 👇 在下面写你的代码（10-15 行）
    # 1. 造消息 + 调 LLM（带 TOOLS）
    # 2. 不需要工具 → 直接返回
    # 3. 需要工具 → append tool_call → for 执行 → append 结果
    # 4. 再调一次 LLM（不带 TOOLS）→ 返回最终答案
    pass


# ── 测试 ──
def test5():
    print("--- 测试 1：不需要工具 ---")
    ans = run_agent("你好，你是谁？")
    print(f"🤖 {ans}\n")

    print("--- 测试 2：需要工具 ---")
    ans = run_agent("15乘以37等于多少")
    print(f"🤖 {ans}")
    # 预期输出：15乘以37等于 555。


# ═══════════════════════════════════════════════════════════
# 运行测试（取消注释来跑）
# ═══════════════════════════════════════════════════════════

if __name__ == "__main__":
    # test1()   # 练完练习1取消这行注释
    # test2()   # 练完练习2取消这行注释
    # test3()   # 练完练习3取消这行注释
    # test4()   # 练完练习4取消这行注释
    # test5()   # 练完练习5取消这行注释
    print("取消注释对应的 test() 来跑测试。从 test1() 开始！")
