"""MeetSpotAgent - 智能会面地点推荐 Agent

基于 ReAct 模式实现的智能推荐代理，通过工具调用完成地点推荐任务。
"""

import json
from typing import Any, Dict, List, Optional

from pydantic import Field

from app.agent.base import BaseAgent
from app.agent.tools import (
    CalculateCenterTool,
    GeocodeTool,
    GenerateRecommendationTool,
    SearchPOITool,
)
from app.llm import LLM
from app.logger import logger
from app.schema import AgentState, Message
from app.tool.tool_collection import ToolCollection


SYSTEM_PROMPT = """你是 MeetSpot 智能会面助手，帮助用户找到最佳会面地点。

## 你的能力
你可以使用以下工具来完成任务：

1. **geocode** - 地理编码
   - 将地址转换为经纬度坐标
   - 支持大学简称（北大、清华）、地标、商圈等
   - 返回坐标和格式化地址

2. **calculate_center** - 计算中心点
   - 计算多个位置的几何中心
   - 作为最佳会面位置的参考点
   - 使用球面几何确保精确

3. **search_poi** - 搜索场所
   - 在中心点附近搜索各类场所
   - 支持咖啡馆、餐厅、图书馆、健身房等
   - 返回名称、地址、评分、距离等

4. **generate_recommendation** - 生成推荐
   - 分析搜索结果
   - 根据评分、距离、用户需求排序
   - 生成个性化推荐理由

## 工作流程
请按以下步骤执行：

1. **理解任务** - 分析用户提供的位置和需求
2. **地理编码** - 依次对每个地址使用 geocode 获取坐标
3. **计算中心** - 使用 calculate_center 计算最佳会面点
4. **搜索场所** - 使用 search_poi 在中心点附近搜索
5. **生成推荐** - 使用 generate_recommendation 生成最终推荐

## 输出要求
- 推荐 3-5 个最佳场所
- 为每个场所说明推荐理由（距离、评分、特色）
- 考虑用户的特殊需求（停车、安静、商务等）
- 使用中文回复

## 注意事项
- 确保在调用工具前已获取所有必要参数
- 如果地址解析失败，提供具体的错误信息和建议
- 如果搜索无结果，尝试调整搜索关键词或扩大半径
"""


class MeetSpotAgent(BaseAgent):
    """MeetSpot 智能会面推荐 Agent

    基于 ReAct 模式的智能代理，通过 think() -> act() 循环完成推荐任务。
    """

    name: str = "MeetSpotAgent"
    description: str = "智能会面地点推荐助手"

    system_prompt: str = SYSTEM_PROMPT
    next_step_prompt: str = "请继续执行下一步，或者如果已完成所有工具调用，请生成最终推荐结果。"

    max_steps: int = 15  # 允许更多步骤以完成复杂任务

    # 工具集合
    available_tools: ToolCollection = Field(default=None)

    # 当前工具调用
    tool_calls: List[Any] = Field(default_factory=list)

    # 存储中间结果
    geocode_results: List[Dict] = Field(default_factory=list)
    center_point: Optional[Dict] = None
    search_results: List[Dict] = Field(default_factory=list)

    class Config:
        arbitrary_types_allowed = True
        extra = "allow"

    def __init__(self, **data):
        super().__init__(**data)
        # 初始化工具集合
        if self.available_tools is None:
            self.available_tools = ToolCollection(
                GeocodeTool(),
                CalculateCenterTool(),
                SearchPOITool(),
                GenerateRecommendationTool()
            )

    async def step(self) -> str:
        """执行一步: think + act

        Returns:
            步骤执行结果的描述
        """
        # Think: 决定下一步行动
        should_continue = await self.think()

        if not should_continue:
            self.state = AgentState.FINISHED
            return "任务完成"

        # Act: 执行工具调用
        result = await self.act()
        return result

    async def think(self) -> bool:
        """思考阶段 - 决定下一步行动

        使用 LLM 分析当前状态，决定是否需要调用工具以及调用哪个工具。

        Returns:
            是否需要继续执行
        """
        # 构建消息
        messages = self.memory.messages.copy()

        # 添加提示引导下一步
        if self.next_step_prompt and self.current_step > 1:
            messages.append(Message.user_message(self.next_step_prompt))

        # 调用 LLM 获取响应
        response = await self.llm.ask_tool(
            messages=messages,
            system_msgs=[Message.system_message(self.system_prompt)],
            tools=self.available_tools.to_params(),
            tool_choice="auto"
        )

        if response is None:
            logger.warning("LLM 返回空响应")
            return False

        # 提取工具调用和内容
        self.tool_calls = response.tool_calls or []
        content = response.content or ""

        logger.info(f"Agent 思考: {content[:200]}..." if len(content) > 200 else f"Agent 思考: {content}")

        if self.tool_calls:
            tool_names = [tc.function.name for tc in self.tool_calls]
            logger.info(f"选择工具: {tool_names}")

        # 保存 assistant 消息到记忆
        if self.tool_calls:
            # 带工具调用的消息
            tool_calls_data = [
                {
                    "id": tc.id,
                    "type": "function",
                    "function": {
                        "name": tc.function.name,
                        "arguments": tc.function.arguments
                    }
                }
                for tc in self.tool_calls
            ]
            self.memory.add_message(Message(
                role="assistant",
                content=content,
                tool_calls=tool_calls_data
            ))
        elif content:
            # 纯文本消息（可能是最终回复）
            self.memory.add_message(Message.assistant_message(content))
            # 如果没有工具调用且有内容，可能是最终回复
            if "推荐" in content and len(content) > 100:
                return False  # 结束循环

        return bool(self.tool_calls) or bool(content)

    async def act(self) -> str:
        """行动阶段 - 执行工具调用

        执行思考阶段决定的工具调用，并将结果添加到记忆。

        Returns:
            工具执行结果的描述
        """
        if not self.tool_calls:
            # 没有工具调用，返回最后一条消息的内容
            return self.memory.messages[-1].content or "无操作"

        results = []
        for call in self.tool_calls:
            tool_name = call.function.name
            tool_args = call.function.arguments

            try:
                # 解析参数
                args = json.loads(tool_args) if isinstance(tool_args, str) else tool_args

                # 执行工具
                logger.info(f"执行工具: {tool_name}, 参数: {args}")
                result = await self.available_tools.execute(name=tool_name, tool_input=args)

                # 保存中间结果
                self._save_intermediate_result(tool_name, result, args)

                # 将工具结果添加到记忆
                result_str = str(result)
                self.memory.add_message(Message.tool_message(
                    content=result_str,
                    tool_call_id=call.id,
                    name=tool_name
                ))

                logger.info(f"工具 {tool_name} 完成")
                results.append(f"{tool_name}: 成功")

            except Exception as e:
                error_msg = f"工具执行失败: {str(e)}"
                logger.error(f"{tool_name} {error_msg}")

                # 添加错误消息到记忆
                self.memory.add_message(Message.tool_message(
                    content=error_msg,
                    tool_call_id=call.id,
                    name=tool_name
                ))
                results.append(f"{tool_name}: 失败 - {str(e)}")

        return " | ".join(results)

    def _save_intermediate_result(self, tool_name: str, result: Any, args: Dict) -> None:
        """保存工具执行的中间结果

        Args:
            tool_name: 工具名称
            result: 工具执行结果
            args: 工具参数
        """
        try:
            # 解析结果
            if hasattr(result, 'output') and result.output:
                data = json.loads(result.output) if isinstance(result.output, str) else result.output
            else:
                return

            if tool_name == "geocode" and data:
                self.geocode_results.append({
                    "address": args.get("address", ""),
                    "lng": data.get("lng"),
                    "lat": data.get("lat"),
                    "formatted_address": data.get("formatted_address", "")
                })

            elif tool_name == "calculate_center" and data:
                self.center_point = data.get("center")

            elif tool_name == "search_poi" and data:
                places = data.get("places", [])
                self.search_results.extend(places)

        except Exception as e:
            logger.debug(f"保存中间结果时出错: {e}")

    async def recommend(
        self,
        locations: List[str],
        keywords: str = "咖啡馆",
        requirements: str = ""
    ) -> Dict:
        """执行推荐任务

        这是 Agent 的主要入口方法，接收用户输入并返回推荐结果。

        Args:
            locations: 参与者位置列表
            keywords: 搜索关键词（场所类型）
            requirements: 用户特殊需求

        Returns:
            包含推荐结果的字典
        """
        # 重置状态
        self.geocode_results = []
        self.center_point = None
        self.search_results = []
        self.current_step = 0
        self.state = AgentState.IDLE
        self.memory.clear()

        # 构建任务描述
        locations_str = "、".join(locations)
        task = f"""请帮我找到适合会面的地点：

**参与者位置**：{locations_str}
**想找的场所类型**：{keywords}
**特殊需求**：{requirements or "无特殊需求"}

请按照工作流程执行：
1. 先用 geocode 工具获取每个位置的坐标
2. 用 calculate_center 计算中心点
3. 用 search_poi 搜索附近的 {keywords}
4. 用 generate_recommendation 生成推荐

最后请用中文总结推荐结果。"""

        # 执行任务
        result = await self.run(task)

        # 格式化返回结果
        return self._format_result(result)

    def _format_result(self, raw_result: str) -> Dict:
        """格式化最终结果

        Args:
            raw_result: Agent 执行的原始结果

        Returns:
            格式化的结果字典
        """
        # 获取最后一条 assistant 消息作为最终推荐
        final_recommendation = ""
        for msg in reversed(self.memory.messages):
            if msg.role == "assistant" and msg.content:
                final_recommendation = msg.content
                break

        return {
            "success": self.state == AgentState.IDLE,  # IDLE 表示正常完成
            "recommendation": final_recommendation,
            "geocode_results": self.geocode_results,
            "center_point": self.center_point,
            "search_results": self.search_results[:10],  # 限制返回数量
            "steps_executed": self.current_step,
            "raw_output": raw_result
        }


# 创建默认 Agent 实例的工厂函数
def create_meetspot_agent() -> MeetSpotAgent:
    """创建 MeetSpotAgent 实例

    Returns:
        配置好的 MeetSpotAgent 实例
    """
    return MeetSpotAgent()
