"""Data API 便捷函数。

提供简化的函数接口用于快速访问数据 API。
"""

import logging
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, Field

from hezor_common.data_model.searching.data.api_models import (
    SearchResponse,
    ToolSchema,
)
from hezor_common.transfer.datahub_sdk.base.constants import (
    BUILD_ARGS_PROMPT,
    DEFAULT_ARGS_FILL_TIPS,
    DEFAULT_LLM_API_BASE_URL,
    DEFAULT_LLM_API_KEY,
    DEFAULT_LLM_MODEL_ID,
)


class ParameterValidationError(Exception):
    """参数验证错误。"""

    pass


if TYPE_CHECKING:
    from hezor_common.utilities.agentic.deps import Model

logger = logging.getLogger(__name__)

EXAMPLE_TOOL_ARGS = """
{
  "args": {
    "store_code": "1",
    "start_year": 2026,
    "start_month": 12,
    "end_year": 2026,
    "end_month": 12
}
"""


class ToolArgs(BaseModel):
    """工具参数结构化输出模型。"""

    args: dict[str, Any] = Field(
        ...,
        description="工具参数字典，键为参数名，值为参数值。将所有需要的参数放在这个字典中。",
    )


async def build_args(
    search_response: SearchResponse,
    args_fill_tips: str = DEFAULT_ARGS_FILL_TIPS,
    model: "Model | None" = None,
    max_retries: int = 3,
    model_kwargs: dict[str, Any] | None = None,
    agent_kwargs: dict[str, Any] | None = None,
    tool_index: int = 0,
    query: str = "",
) -> dict[str, Any]:
    """使用Agent构建单个工具的参数。

    基于搜索到的工具schema和自然语言提示，使用AI Agent自动填充参数。

    Parameters
    ----------
    search_response : SearchResponse
        工具搜索响应，包含工具的参数说明
    args_fill_tips : str
        自然语言参数填写说明，默认为通用提示
    model : Model | None
        使用的AI模型，默认为 OpenAIChat(id="hezor2")
    max_retries : int
        验证失败时的最大重试次数，默认为3
    model_kwargs : dict[str, Any] | None
        传递给 OpenAIChat 构造函数的参数（仅在model为None时使用）。
        常用参数包括：
        - id (str): 模型ID，如 "gpt-4o", "gpt-4", "hezor2"
        - api_key (str): OpenAI API密钥（默认使用环境变量 OPENAI_API_KEY）
        - temperature (float): 控制随机性，范围 0.0-2.0
        - max_completion_tokens (int): 最大生成token数
        - base_url (str): API基础URL
        完整参数列表参见 OpenAIChat 文档
    agent_kwargs : dict[str, Any] | None
        传递给 Agent 构造函数的额外参数。
        常用参数包括：
        - debug_mode (bool): 是否启用调试模式，默认为 True
        - instructions (list[str]): 自定义指令列表
        - description (str): Agent 描述
        完整参数列表参见 Agent 文档
    tool_index : int
        要构建参数的工具索引，默认为0（第一个工具）
    query : str
        用户原始查询，用于提供LLM更多上下文信息

    Returns
    -------
    dict[str, Any]
        验证通过的工具参数字典

    Raises
    ------
    ValueError
        如果搜索结果为空或经过max_retries次重试后仍然验证失败
    IndexError
        如果tool_index超出范围

    Examples
    --------
    >>> # 基本使用（使用默认模型和环境变量中的API密钥）
    >>> search_resp = await search_tools("查询天气")
    >>> args = await build_args(
    ...     search_resp,
    ...     args_fill_tips="查询北京的天气，需要返回温度和湿度"
    ... )
    >>> execute_resp = await execute_tool(search_resp.tools[0].name, args)

    >>> # 使用自定义模型参数
    >>> args = await build_args(
    ...     search_resp,
    ...     args_fill_tips="查询北京的天气",
    ...     model_kwargs={"id": "gpt-4o", "temperature": 0.7}
    ... )

    >>> # 构建第二个工具的参数
    >>> args = await build_args(
    ...     search_resp,
    ...     args_fill_tips="查询上海的天气",
    ...     tool_index=1
    ... )
    """
    # 运行时导入，避免导入时的依赖问题
    from hezor_common.utilities.agentic.deps import Agent, OpenAIChat

    if not search_response.tools:
        raise ValueError("搜索结果为空，无法构建参数")

    # 获取指定索引的工具
    if tool_index < 0 or tool_index >= len(search_response.tools):
        raise IndexError(
            f"工具索引 {tool_index} 超出范围，可用范围: 0-{len(search_response.tools) - 1}"
        )

    tool_schema = search_response.tools[tool_index]

    # 创建默认模型
    if model is None:
        default_kwargs: dict[str, Any] = {
            "id": DEFAULT_LLM_MODEL_ID,
            "api_key": DEFAULT_LLM_API_KEY,
            "base_url": DEFAULT_LLM_API_BASE_URL,
        }
        # 合并用户提供的额外参数
        if model_kwargs:
            default_kwargs.update(model_kwargs)
        model = OpenAIChat(**default_kwargs)

    # 构建提示信息
    prompt = _build_args_prompt(tool_schema, args_fill_tips, query=query)

    # 创建Agent进行参数填充
    # 注意：不使用 output_schema，因为某些模型可能不支持结构化输出
    # 改为在 prompt 中明确要求 JSON 格式，然后手动解析
    default_agent_kwargs: dict[str, Any] = {
        "model": model,
        "description": "工具参数填充助手",
        "instructions": [
            "你是一个专业的工具参数填充助手",
            "根据工具的参数说明和用户的需求，填写合适的参数值",
            "必须严格按照参数的类型和要求填写",
            "如果某个参数是必需的但无法从用户需求中推断，请使用合理的默认值",
            "",
            "重要：你必须返回纯 JSON 格式，不要包含任何其他文本",
            "JSON 格式示例:",
            EXAMPLE_TOOL_ARGS,
            "",
            "你的回复必须是有效的 JSON，以 { 开头，以 } 结尾",
        ],
    }
    # 合并用户提供的额外参数
    if agent_kwargs:
        default_agent_kwargs.update(agent_kwargs)
    agent = Agent(**default_agent_kwargs)

    # 尝试生成并验证参数
    for attempt in range(max_retries):
        try:
            # 运行Agent获取结构化输出
            run_response = agent.run(prompt)

            # 检查响应
            if not run_response or not run_response.content:
                logger.warning(f"Agent返回空响应 (尝试 {attempt + 1}/{max_retries})")
                if attempt < max_retries - 1:
                    continue
                raise ValueError("Agent无法生成有效参数")

            # 提取参数
            try:
                args = _parse_agent_response(run_response.content)
            except ValueError as e:
                logger.warning(f"响应解析失败 (尝试 {attempt + 1}/{max_retries}): {e}")
                if attempt < max_retries - 1:
                    continue
                raise ValueError(f"Agent响应格式不正确: {e}") from e

            # 检查参数是否为空（仅当有必需参数时）
            required_params = tool_schema.params.required or []
            if not args and required_params:
                error_msg = f"Agent 返回空参数，但工具需要以下必需参数: {required_params}"
                logger.warning(f"{error_msg} (尝试 {attempt + 1}/{max_retries})")
                logger.debug(f"完整响应内容: {run_response.content}")
                if attempt < max_retries - 1:
                    # 更新提示，包含空参数错误信息
                    prompt = _build_args_prompt(
                        tool_schema,
                        args_fill_tips,
                        query=query,
                        previous_error=error_msg,
                    )
                    continue
                raise ValueError(f"Agent 返回空参数，但需要必需参数: {required_params}")

            # 验证参数
            _validate_args(args, tool_schema)

            logger.info(f"参数构建成功: {args}")
            return args

        except ParameterValidationError as e:
            error_msg = f"参数验证失败: {e}"
            logger.warning(f"{error_msg} (尝试 {attempt + 1}/{max_retries})")
            if attempt < max_retries - 1:
                # 更新提示，包含详细的验证错误信息
                prompt = _build_args_prompt(
                    tool_schema,
                    args_fill_tips,
                    query=query,
                    previous_error=error_msg,
                )
            else:
                raise ValueError(f"经过{max_retries}次重试后参数验证仍失败: {e}") from e
        except Exception as e:
            logger.error(f"参数构建失败 (尝试 {attempt + 1}/{max_retries}): {e}")
            if attempt < max_retries - 1:
                continue
            raise

    raise ValueError(f"经过{max_retries}次重试后仍无法构建有效参数")


async def build_args_for_all_tools(
    search_response: SearchResponse,
    args_fill_tips: str = "请根据工具的参数说明填写合适的参数值",
    model: "Model | None" = None,
    max_retries: int = 3,
    model_kwargs: dict[str, Any] | None = None,
    agent_kwargs: dict[str, Any] | None = None,
    query: str = "",
) -> dict[str, dict[str, Any]]:
    """使用Agent为所有工具构建参数。

    基于搜索到的所有工具schema和自然语言提示，使用AI Agent自动填充每个工具的参数。

    Parameters
    ----------
    search_response : SearchResponse
        工具搜索响应，包含多个工具的参数说明
    args_fill_tips : str
        自然语言参数填写说明，默认为通用提示
    model : Model | None
        使用的AI模型，默认为 OpenAIChat(id="hezor2")
    max_retries : int
        验证失败时的最大重试次数，默认为3
    model_kwargs : dict[str, Any] | None
        传递给 OpenAIChat 构造函数的参数（仅在model为None时使用）
    agent_kwargs : dict[str, Any] | None
        传递给 Agent 构造函数的额外参数
    query : str
        用户原始查询，用于提供LLM更多上下文信息

    Returns
    -------
    dict[str, dict[str, Any]]
        字典，键为工具名称，值为该工具的参数字典

    Raises
    ------
    ValueError
        如果搜索结果为空或某个工具的参数构建失败

    Examples
    --------
    >>> # 为所有搜索到的工具构建参数
    >>> search_resp = await search_tools("查询天气")
    >>> all_args = await build_args_for_all_tools(
    ...     search_resp,
    ...     args_fill_tips="查询北京的天气，需要返回温度和湿度"
    ... )
    >>> # all_args = {"get_weather": {"city": "北京", ...}, "get_forecast": {...}}
    >>> for tool_name, args in all_args.items():
    ...     result = await execute_tool(tool_name, args)
    """
    if not search_response.tools:
        raise ValueError("搜索结果为空，无法构建参数")

    # 为每个工具构建参数
    all_args: dict[str, dict[str, Any]] = {}

    for index, tool in enumerate(search_response.tools):
        logger.info(f"正在为工具 '{tool.name}' (索引 {index}) 构建参数...")

        try:
            args = await build_args(
                search_response=search_response,
                args_fill_tips=args_fill_tips,
                model=model,
                max_retries=max_retries,
                model_kwargs=model_kwargs,
                agent_kwargs=agent_kwargs,
                tool_index=index,
                query=query,
            )
            all_args[tool.name] = args
            logger.info(f"工具 '{tool.name}' 参数构建成功: {args}")

        except Exception as e:
            logger.error(f"工具 '{tool.name}' 参数构建失败: {e}")
            # 继续处理其他工具，但记录错误
            raise ValueError(f"工具 '{tool.name}' 参数构建失败: {e}") from e

    return all_args


def _build_args_prompt(
    tool_schema: ToolSchema,
    args_fill_tips: str,
    query: str = "",
    previous_error: str | None = None,
) -> str:
    """构建Agent提示词。

    Parameters
    ----------
    tool_schema : ToolSchema
        工具schema
    args_fill_tips : str
        用户提供的参数填写提示
    query : str
        用户原始查询
    previous_error : str | None
        上次验证失败的错误信息

    Returns
    -------
    str
        构建的提示词
    """

    return BUILD_ARGS_PROMPT(tool_schema, args_fill_tips, query, previous_error)


def _parse_agent_response(content: Any) -> dict[str, Any]:
    """解析 Agent 响应，提取参数字典。

    Parameters
    ----------
    content : Any
        Agent 返回的响应内容，可能是字符串、字典或 ToolArgs 对象

    Returns
    -------
    dict[str, Any]
        解析出的参数字典

    Raises
    ------
    ValueError
        如果无法解析响应

    Examples
    --------
    >>> _parse_agent_response('{"args": {"city": "Beijing"}}')
    {'city': 'Beijing'}
    >>> _parse_agent_response({"args": {"city": "Beijing"}})
    {'city': 'Beijing'}
    """
    import json

    logger.debug(f"解析 Agent 响应，类型: {type(content)}")

    def _extract_args_from_json(json_str: str, method: str) -> dict[str, Any]:
        """从 JSON 字符串提取参数。"""
        parsed = json.loads(json_str)
        args = parsed["args"] if isinstance(parsed, dict) and "args" in parsed else parsed
        logger.debug(f"{method}解析成功: {args}")
        return args

    # 处理字符串响应（JSON 文本）
    if isinstance(content, str):
        import re

        json_str = content.strip()

        # 1. 首先尝试直接解析完整内容
        try:
            return _extract_args_from_json(json_str, "直接")
        except json.JSONDecodeError:
            logger.debug("直接解析失败，尝试提取 JSON")

        # 2. 尝试查找 JSON 对象（从第一个 { 到最后一个 }）
        if "{" in content and "}" in content:
            start = content.find("{")
            end = content.rfind("}") + 1
            json_str = content[start:end]
            logger.debug("从文本中提取 JSON 对象")

            try:
                return _extract_args_from_json(json_str, "提取 JSON 对象")
            except json.JSONDecodeError:
                logger.debug("JSON 对象提取解析失败，尝试代码块提取")

        # 3. 尝试提取 markdown 代码块中的 JSON
        code_block_match = re.search(r"```(?:json)?\s*\n?(.*?)\n?```", content, re.DOTALL)
        if code_block_match:
            json_str = code_block_match.group(1).strip()
            logger.debug("从 markdown 代码块提取 JSON")

            try:
                return _extract_args_from_json(json_str, "代码块")
            except json.JSONDecodeError:
                pass

        # 所有尝试都失败，抛出详细错误
        raise ValueError(
            f"JSON 解析失败，已尝试所有方法\n"
            f"原始内容长度: {len(content)}\n"
            f"内容预览: {content[:200]}..."
        )

    # 处理字典响应
    if isinstance(content, dict):
        args = content.get("args", content)
        logger.debug(f"从字典提取参数: {args}")
        return args

    # 处理 ToolArgs 对象（结构化输出）
    if isinstance(content, ToolArgs):
        args = content.args
        logger.debug(f"从 ToolArgs 提取参数: {args}")
        return args

    # 无法识别的类型
    raise ValueError(f"无法解析响应类型: {type(content)}, 内容: {content}")


def _validate_args(args: dict[str, Any], tool_schema: ToolSchema) -> None:
    """验证参数是否符合工具schema。

    Parameters
    ----------
    args : dict[str, Any]
        待验证的参数
    tool_schema : ToolSchema
        工具schema

    Raises
    ------
    ParameterValidationError
        参数验证失败
    """
    # 检查必需参数
    required = tool_schema.params.required or []
    for param in required:
        if param not in args:
            raise ParameterValidationError(f"缺少必需参数: {param}")

    # 检查参数类型
    properties = tool_schema.params.props or {}
    for param_name, param_value in args.items():
        if param_name not in properties:
            logger.warning(f"参数 '{param_name}' 不在工具定义中")
            continue

        param_def = properties[param_name]
        expected_type = param_def.type

        if expected_type:
            _check_param_type(param_name, param_value, expected_type)


def _check_param_type(param_name: str, param_value: Any, expected_type: str) -> None:
    """检查参数类型是否匹配。

    Parameters
    ----------
    param_name : str
        参数名
    param_value : Any
        参数值
    expected_type : str
        期望的JSON Schema类型 (string, number, integer, boolean, array, object)

    Raises
    ------
    ParameterValidationError
        类型不匹配
    """
    type_map = {
        "string": str,
        "number": (int, float),
        "integer": int,
        "boolean": bool,
        "array": list,
        "object": dict,
    }

    python_type = type_map.get(expected_type)
    if python_type and not isinstance(param_value, python_type):
        raise ParameterValidationError(
            f"参数 '{param_name}' 类型错误: 期望 {expected_type}, "
            f"实际为 {type(param_value).__name__}"
        )
