"""Data API 客户端。

用于与数据 API 网关进行交互，支持工具搜索和执行。
"""

import logging
from pathlib import Path
from typing import Any

import httpx

from hezor_common.data_model.searching.data.api_models import (
    ExecuteRequest,
    ExecuteResponse,
    SearchResponse,
)
from hezor_common.transfer.datahub_sdk.base.constants import (
    DEFAULT_DATA_API_BASE_URL,
    DEFAULT_DATA_API_KEY,
)
from hezor_common.transfer.datahub_sdk.base.meta_info import MetaInfo

logger = logging.getLogger(__name__)


class DataAPIClient:
    """数据API客户端。

    用于与数据API网关进行交互，支持工具搜索和执行。
    """

    def __init__(
        self,
        base_url: str = DEFAULT_DATA_API_BASE_URL,
        timeout: float = 120.0,
        api_key: str | None = DEFAULT_DATA_API_KEY,
        meta_info: MetaInfo | None = None,
        private_key_path: str | Path | None = None,
        password: bytes | None = None,
        meta_info_expires_in: int = 3600,
    ):
        """初始化客户端。

        Parameters
        ----------
        base_url : str
            API基础URL
        timeout : float
            请求超时时间（秒）
        api_key : str | None
            API密钥，用于Bearer token认证。如果为None，则不添加Authorization头
        meta_info : MetaInfo | None
            元信息对象，用于生成请求头。如果为None，则不添加元信息头
        private_key_path : str | Path | None
            私钥文件路径，用于JWT签名。如果meta_info不为None，则必须提供
        password : bytes | None
            私钥密码，如果私钥加密则需要提供
        meta_info_expires_in : int
            MetaInfo JWT token过期时间（秒），默认3600秒
        """
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout
        self.api_key = api_key
        self.meta_info = meta_info
        self.private_key_path = private_key_path
        self.password = password
        self.meta_info_expires_in = meta_info_expires_in
        self._client: httpx.AsyncClient | None = None

    async def __aenter__(self) -> "DataAPIClient":
        """异步上下文管理器入口。"""
        self._client = httpx.AsyncClient(timeout=self.timeout)
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """异步上下文管理器出口。"""
        if self._client:
            await self._client.aclose()
            self._client = None

    @property
    def client(self) -> httpx.AsyncClient:
        """获取HTTP客户端。

        Returns
        -------
        httpx.AsyncClient
            HTTP客户端实例

        Raises
        ------
        RuntimeError
            如果客户端未初始化
        """
        if self._client is None:
            msg = "Client not initialized. Use 'async with' context manager."
            raise RuntimeError(msg)
        return self._client

    def _get_headers(self) -> dict[str, str]:
        """获取请求头。

        默认添加Content-Type和Authorization（如果配置了api_key）头。
        如果配置了meta_info和private_key_path，则额外添加包含JWT的X-META-INFO头。

        Returns
        -------
        dict[str, str]
            请求头字典

        Raises
        ------
        ValueError
            如果提供了meta_info但未提供private_key_path
        """
        # 默认请求头
        headers = {
            "Content-Type": "application/json",
        }

        # 添加 Authorization 头
        if self.api_key is not None:
            headers["Authorization"] = f"Bearer {self.api_key}"
            logger.info(f"Added Authorization header, api_key: {self.api_key[:6]}***")

        # 添加 MetaInfo JWT 头
        if self.meta_info is not None:
            if self.private_key_path is None:
                msg = "private_key_path is required when meta_info is provided"
                raise ValueError(msg)

            meta_header = self.meta_info.to_request_header(
                private_key_path=self.private_key_path,
                password=self.password,
                expires_in=self.meta_info_expires_in,
            )
            headers.update(meta_header)
            logger.info(
                f"Added MetaInfo JWT header, meta_info: {meta_header['X-META-INFO'][:10]}***"
            )

        return headers

    async def search_tools(
        self,
        query: str,
        top_k: int = 3,
    ) -> SearchResponse:
        """搜索工具接口。

        根据语义搜索工具，返回匹配的工具定义。

        Parameters
        ----------
        query : str
            搜索查询字符串
        top_k : int
            返回的最大工具数量，默认为3

        Returns
        -------
        SearchResponse
            搜索响应，包含匹配的工具列表

        Raises
        ------
        httpx.HTTPError
            HTTP请求失败时抛出
        ValueError
            响应数据格式错误时抛出

        Examples
        --------
        >>> async with DataAPIClient() as client:
        ...     response = await client.search_tools("查询天气", top_k=5)
        ...     for tool in response.tools:
        ...         print(f"Tool: {tool.name} - {tool.desc}")
        """
        url = f"{self.base_url}/api/search"
        params = {"query": query, "top_k": top_k}

        logger.info(f"Searching tools with query: {query}, top_k: {top_k}")

        try:
            headers = self._get_headers()
            response = await self.client.get(url, params=params, headers=headers)
            response.raise_for_status()

            data = response.json()
            search_response = SearchResponse.model_validate(data)

            logger.info(f"Found {len(search_response.tools)} tools")
            return search_response

        except httpx.HTTPError as e:
            logger.error(f"HTTP error during tool search: {e}")
            raise
        except Exception as e:
            logger.error(f"Error parsing search response: {e}")
            raise ValueError(f"Invalid search response format: {e}") from e

    async def execute_tool(
        self,
        tool_name: str,
        args: dict[str, Any],
    ) -> ExecuteResponse:
        """执行工具接口。

        执行指定的工具并返回结果。

        Parameters
        ----------
        tool_name : str
            工具名称
        args : dict[str, Any]
            工具参数字典

        Returns
        -------
        ExecuteResponse
            执行响应，包含执行结果数据

        Raises
        ------
        httpx.HTTPError
            HTTP请求失败时抛出
        ValueError
            响应数据格式错误时抛出

        Examples
        --------
        >>> async with DataAPIClient() as client:
        ...     response = await client.execute_tool(
        ...         tool_name="get_weather",
        ...         args={"city": "北京", "date": "2025-12-18"}
        ...     )
        ...     print(f"Result: {response.data}")
        """
        url = f"{self.base_url}/api/execute"

        # 构造请求
        request = ExecuteRequest(tool=tool_name, args=args)

        logger.info(f"Executing tool: {tool_name} with args: {args}")

        try:
            headers = self._get_headers()
            response = await self.client.post(
                url,
                json=request.model_dump(),
                headers=headers,
            )
            response.raise_for_status()

            data = response.json()
            execute_response = ExecuteResponse.model_validate(data)

            logger.info(f"Tool execution completed: {tool_name}")
            return execute_response

        except httpx.HTTPError as e:
            logger.error(f"HTTP error during tool execution: {e}")
            raise
        except Exception as e:
            logger.error(f"Error parsing execution response: {e}")
            raise ValueError(f"Invalid execution response format: {e}") from e

    async def execute_tool_from_json(
        self,
        request_json: str,
    ) -> ExecuteResponse:
        """从JSON字符串执行工具接口。

        从JSON字符串构建ExecuteRequest并执行工具。

        Parameters
        ----------
        request_json : str
            JSON格式的执行请求字符串

        Returns
        -------
        ExecuteResponse
            执行响应，包含执行结果数据

        Raises
        ------
        httpx.HTTPError
            HTTP请求失败时抛出
        ValueError
            JSON格式错误或响应数据格式错误时抛出

        Examples
        --------
        >>> async with DataAPIClient() as client:
        ...     json_str = '{"tool": "get_weather", "args": {"city": "北京"}}'
        ...     response = await client.execute_tool_from_json(json_str)
        ...     print(f"Result: {response.data}")
        """
        url = f"{self.base_url}/api/execute"

        try:
            # 从JSON字符串构建请求
            request = ExecuteRequest.model_validate_json(request_json)
            logger.info(f"Executing tool from JSON: {request.tool}")

            headers = self._get_headers()
            response = await self.client.post(
                url,
                json=request.model_dump(),
                headers=headers,
            )
            response.raise_for_status()

            data = response.json()
            execute_response = ExecuteResponse.model_validate(data)

            logger.info(f"Tool execution completed: {request.tool}")
            return execute_response

        except httpx.HTTPError as e:
            logger.error(f"HTTP error during tool execution: {e}")
            raise
        except Exception as e:
            logger.error(f"Error parsing request or response: {e}")
            raise ValueError(f"Invalid JSON format or execution response: {e}") from e
