"""Hezor2 API 客户端。

用于与 Hezor2 API 进行交互。
"""

import logging
from pathlib import Path
from typing import Any

import httpx

from hezor_common.data_model.app_config import PullConfigsResponse
from hezor_common.data_model.connect import ConnectRefreshResponse, ConnectVerifyResponse
from hezor_common.data_model.creations.core.creation_generate_result import (
    CreationGenerateResult,
    CreationGenerateResultV2,
)
from hezor_common.data_model.creations.execution.webhook import (
    GenerateReportIdResponseData,
    PublishCreationResponseData,
    ReportMetadata,
)
from hezor_common.data_model.searching import DataRetrieveResult, KnowledgeSearchResult
from hezor_common.data_model.webhook import WebhookActionHelp, WebhookResponse
from hezor_common.transfer.base_sdk import BaseAPIClient, MetaInfo
from hezor_common.transfer.base_sdk.constants import REQ_HEADER_META_INFO_KEY
from hezor_common.transfer.hezor2_sdk.base.constants import (
    DEFAULT_API_BASE_URL,
    DEFAULT_API_KEY,
)

logger = logging.getLogger(__name__)


class Hezor2APIClient(BaseAPIClient):
    """Hezor2 API 客户端。

    用于与 Hezor2 API 进行交互。
    继承自 BaseAPIClient，提供通用的认证和请求头管理功能。

    Examples
    --------
    >>> async with Hezor2APIClient() as client:
    ...     response = await client.get("/api/endpoint")
    ...     data = response.json()
    """

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

        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 签名。如果为 None 且提供了 meta_info，将使用匿名密钥
        password : bytes | None
            私钥密码，如果私钥加密则需要提供
        meta_info_expires_in : int
            MetaInfo JWT token 过期时间（秒），默认 3600 秒
        app_name : str | None
            应用名称，通过 X-APP-NAME 头传递给服务端
        """
        super().__init__(
            base_url=base_url,
            timeout=timeout,
            api_key=api_key,
            meta_info=meta_info,
            private_key_path=private_key_path,
            password=password,
            meta_info_expires_in=meta_info_expires_in,
            app_name=app_name,
        )

    async def _webhook_request(
        self,
        action: str,
        payload: dict[str, Any],
    ) -> WebhookResponse:
        """Send a request to the unified webhook endpoint.

        Parameters
        ----------
        action : str
            Action name (e.g. ``"generate_report_id"``).
        payload : dict[str, Any]
            Action payload.

        Returns
        -------
        WebhookResponse
            Parsed webhook response.

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。
        ValueError
            响应 status 不是预期值时抛出。
        """
        path = "/webhook/"
        body = {"action": action, "payload": payload}

        logger.info("Webhook request: action=%s", action)
        response = await self.post(path, json=body)
        response.raise_for_status()

        resp = WebhookResponse.model_validate(response.json())
        if resp.status == "error":
            raise ValueError(f"Webhook action '{action}' failed: {resp.message or 'unknown error'}")
        return resp

    async def generate_report_id(
        self,
        count: int = 1,
    ) -> list[str]:
        """Generate unique report IDs.

        Parameters
        ----------
        count : int
            Number of IDs to generate (the default is 1).

        Returns
        -------
        list[str]
            Generated report IDs with ``rpt_`` prefix.

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

        Examples
        --------
        >>> async with Hezor2APIClient() as client:
        ...     ids = await client.generate_report_id(count=3)
        ...     print(ids)
        ['rpt_xxx', 'rpt_yyy', 'rpt_zzz']
        """
        data = await self._webhook_request(
            "generate_report_id",
            {"count": count},
        )
        result = GenerateReportIdResponseData.model_validate(data.data)
        logger.info("Generated %d report ID(s): %s", len(result.report_ids), result.report_ids)
        return result.report_ids

    async def publish_creation_report(
        self,
        payload: CreationGenerateResult | CreationGenerateResultV2,
        task_id: str | None = None,
        execution_id: str | None = None,
    ) -> PublishCreationResponseData:
        """发布创作报告到 Webhook。

        将生成的创作报告发送到 Hezor2 API 的统一 webhook
        接口进行处理。

        推荐传入 ``CreationGenerateResultV2``（扁平化结构），
        SDK 会自动使用 ``publish_creation_report_v2`` action。
        传入 ``CreationGenerateResult`` 仍然可用，但该路径
        将在未来版本中废弃。

        Parameters
        ----------
        payload : CreationGenerateResult or CreationGenerateResultV2
            生成的创作结果数据。推荐使用
            ``CreationGenerateResultV2``。
        task_id : str or None
            任务 ID（如 ``"monthly_report"``）。
            为 None 时服务端自动生成。
        execution_id : str or None
            执行 ID（如 ``"exec_2026_01_21_xxx"``）。
            为 None 时服务端自动生成。

        Returns
        -------
        PublishCreationResponseData
            包含 ``report_id``, ``task_id``,
            ``execution_id``。

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

        Examples
        --------
        推荐用法（V2）：

        >>> result = CreationGenerateResultV2(
        ...     creation_id="rpt_xxx",
        ...     title="鮨大山 单店盈利模型",
        ...     full_content="# 报告内容...",
        ...     subject="鮨大山",
        ...     slug="single_store_profit_model",
        ... )
        >>> async with Hezor2APIClient() as client:
        ...     response = await client.publish_creation_report(
        ...         payload=result,
        ...         task_id="monthly_report",
        ...     )
        ...     print(response.report_id)

        从字典或外部数据构建时，使用 ``model_validate``：

        >>> data = {"creation_id": "rpt_xxx", ...}
        >>> result = CreationGenerateResultV2.model_validate(data)

        旧用法（将废弃）：

        >>> async with Hezor2APIClient() as client:
        ...     result = CreationGenerateResult(...)
        ...     response = await client.publish_creation_report(
        ...         payload=result,
        ...     )
        """
        is_v2 = isinstance(payload, CreationGenerateResultV2)
        action = "publish_creation_report_v2" if is_v2 else "publish_creation_report"

        webhook_payload: dict[str, Any] = {
            "creation_result": payload.model_dump(),
        }
        if task_id is not None:
            webhook_payload["task_id"] = task_id
        if execution_id is not None:
            webhook_payload["execution_id"] = execution_id

        resp = await self._webhook_request(action, webhook_payload)
        resp_data = PublishCreationResponseData.model_validate(resp.data)
        logger.info(
            "Creation report published (action=%s): report_id=%s",
            action,
            resp_data.report_id,
        )
        return resp_data

    async def get_report_status(
        self,
        creation_id: str,
        report_id: str,
    ) -> ReportMetadata:
        """查询报告状态。

        Parameters
        ----------
        creation_id : str
            Creation ID。
        report_id : str
            Report ID。

        Returns
        -------
        ReportMetadata
            报告元数据。

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。

        Examples
        --------
        >>> async with Hezor2APIClient() as client:
        ...     resp = await client.get_report_status(
        ...         creation_id="crt_xxx",
        ...         report_id="rpt_xxx",
        ...     )
        ...     print(resp.report_title)
        """
        resp = await self._webhook_request(
            "get_report_status",
            {"creation_id": creation_id, "report_id": report_id},
        )
        resp_data = ReportMetadata.model_validate(resp.data)
        logger.info(
            "Report status retrieved: report_id=%s",
            report_id,
        )
        return resp_data

    async def health_check(self) -> tuple[bool, dict]:
        """执行健康检查。

        调用 GET /health 端点检查服务状态。

        Returns
        -------
        tuple[bool, dict]
            - bool: 健康检查是否通过（基于 response.status == "healthy"）
            - dict: 原始健康检查响应数据

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出

        Examples
        --------
        >>> async with Hezor2APIClient() as client:
        ...     is_healthy, health = await client.health_check()
        ...     if is_healthy:
        ...         print(f"Status: {health.get('status')}")
        """
        path = "/health"

        logger.info("Executing health check")

        try:
            response = await self.get(path)
            response.raise_for_status()

            data = response.json()
            is_healthy = data.get("status") == "healthy"
            logger.info(f"Health check completed: {data}, is_healthy={is_healthy}")
            return is_healthy, data

        except httpx.HTTPError as e:
            logger.error(f"HTTP error during health check: {e}")
            raise

    async def webhook_help(self, action: str) -> WebhookActionHelp:
        """查询 Webhook action 的接口文档。

        Parameters
        ----------
        action : str
            要查询的 action 名称。

        Returns
        -------
        WebhookActionHelp
            包含 action 描述、用法、payload schema
            和 response data schema 的文档信息。

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。

        Examples
        --------
        >>> async with Hezor2APIClient() as client:
        ...     info = await client.webhook_help("generate_report_id")
        ...     print(info.action)
        'generate_report_id'
        """
        path = "/webhook/help"
        logger.info("Webhook help: action=%s", action)
        response = await self.get(path, params={"action": action})
        response.raise_for_status()
        return WebhookActionHelp.model_validate(response.json())

    async def knowledge_retrieve(
        self,
        query: str,
        *,
        top_k: int = 3,
        score_threshold: float = 0.5,
    ) -> KnowledgeSearchResult:
        """执行知识检索 Webhook。

        Parameters
        ----------
        query : str
            检索查询。
        top_k : int
            每个集合返回数量上限。
        score_threshold : float
            相似度阈值。

        Returns
        -------
        KnowledgeSearchResult
            综合知识检索结果。

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。
        ValueError
            响应数据格式错误时抛出。
        """
        payload: dict[str, Any] = {
            "query": query,
            "top_k": top_k,
            "score_threshold": score_threshold,
        }
        resp = await self._webhook_request("knowledge_retrieve", payload)
        return KnowledgeSearchResult.model_validate(resp.data)

    async def data_retrieve(
        self,
        query: str,
        *,
        top_k: int = 1,
    ) -> DataRetrieveResult:
        """执行数据检索 Webhook。

        Parameters
        ----------
        query : str
            数据查询语句。
        top_k : int
            工具搜索返回的最大数量，默认为 1。

        Returns
        -------
        DataRetrieveResult
            各工具执行结果映射。

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。
        ValueError
            响应数据格式错误时抛出。
        """
        payload: dict[str, Any] = {
            "query": query,
            "top_k": top_k,
        }
        resp = await self._webhook_request("data_retrieve", payload)
        return DataRetrieveResult.model_validate(resp.data)

    async def pull_configs(
        self,
        keys: list[str] | None = None,
        global_base_url: str | None = None,
    ) -> PullConfigsResponse:
        """从配置中心拉取配置。

        Parameters
        ----------
        keys : list[str] or None
            指定要拉取的配置 key 列表；为 None 则返回全量配置。
        global_base_url : str or None
            替换配置值中 ``${GLOBAL_BASE_URL}`` 占位符的地址。
            为 None 时服务端自动使用当前 Hezor 实例 URL。

        Returns
        -------
        PullConfigsResponseData
            包含 ``public`` 和 ``user`` 两部分配置。
            调用 ``.merged()`` 可获得合并后的字典（user 优先）。

        Raises
        ------
        httpx.HTTPError
            HTTP 请求失败时抛出。
        ValueError
            响应 status 为 error 时抛出。

        Examples
        --------
        >>> async with Hezor2APIClient() as client:
        ...     data = await client.pull_configs()
        ...     configs = data.merged()
        ...     print(configs)
        {'HEZOR_SDK_VERSION': '0.7.x', 'MY_APP_ID': 'app_12345'}
        """
        webhook_payload: dict[str, Any] = {}
        if keys is not None:
            webhook_payload["keys"] = keys
        if global_base_url is not None:
            webhook_payload["context_variables"] = {"global_base_url": global_base_url}

        resp = await self._webhook_request("pull_configs", webhook_payload)
        result = PullConfigsResponse.model_validate(resp.data)
        logger.info(
            "pull_configs: public=%d keys, user=%d keys",
            len(result.public),
            len(result.user),
        )
        return result

    # ── Connect login ─────────────────────────────────────────────────────────

    def _sign_meta_info_jwt(self) -> str:
        """Sign a fresh meta_info JWT using the client's configured key material.

        Returns
        -------
        str
            Raw JWT string.

        Raises
        ------
        ValueError
            If metaInfo or private key is not configured on this client.
        """
        headers = self._get_headers()
        jwt = headers.get(REQ_HEADER_META_INFO_KEY)
        if not jwt:
            msg = (
                "Cannot sign meta_info JWT: meta_info or private key not configured on this client"
            )
            raise ValueError(msg)
        return jwt

    async def connect_verify(self, callback_url: str) -> ConnectVerifyResponse:
        """Verify a Connect app identity.

        Calls ``POST /auth/connect/verify`` with ``app_name``, signed
        ``meta_info`` JWT, and ``callback_url``.

        Parameters
        ----------
        callback_url : str
            The callback URL to verify (must be in the app's redirect_uris).

        Returns
        -------
        ConnectVerifyResponse
            Verification result.

        Raises
        ------
        ValueError
            If ``app_name`` is not configured.
        httpx.HTTPError
            If the HTTP request fails.
        """
        if not self.app_name:
            msg = "app_name is required for Connect verify"
            raise ValueError(msg)

        meta_info_jwt = self._sign_meta_info_jwt()

        response = await self.post(
            "/auth/connect/verify",
            skip_auth=True,
            json={
                "app_name": self.app_name,
                "meta_info": meta_info_jwt,
                "callback_url": callback_url,
            },
        )
        response.raise_for_status()
        return ConnectVerifyResponse.model_validate(response.json())

    async def connect_refresh(self, refresh_token: str) -> ConnectRefreshResponse:
        """Refresh a Connect token.

        Calls ``POST /auth/connect/refresh`` with ``app_name``, a freshly
        signed ``meta_info`` JWT, and the ``refresh_token``.

        Parameters
        ----------
        refresh_token : str
            The refresh token obtained from Connect login callback.

        Returns
        -------
        ConnectRefreshResponse
            New tokens and user information.

        Raises
        ------
        ValueError
            If ``app_name`` is not configured.
        httpx.HTTPError
            If the HTTP request fails.
        """
        if not self.app_name:
            msg = "app_name is required for Connect refresh"
            raise ValueError(msg)

        meta_info_jwt = self._sign_meta_info_jwt()

        response = await self.post(
            "/auth/connect/refresh",
            skip_auth=True,
            json={
                "app_name": self.app_name,
                "meta_info": meta_info_jwt,
                "refresh_token": refresh_token,
            },
        )
        response.raise_for_status()
        return ConnectRefreshResponse.model_validate(response.json())

    def build_connect_url(self, frontend_url: str, callback_url: str) -> str:
        """Build a Connect login URL.

        Signs the MetaInfo JWT and constructs the full URL that a user
        should be redirected to in order to begin the Connect login flow.

        Parameters
        ----------
        frontend_url : str
            The Hezor frontend base URL (e.g. ``"https://your-hezor-domain.com"``).
        callback_url : str
            The callback URL to redirect to after login.

        Returns
        -------
        str
            Full Connect login URL with query parameters.

        Raises
        ------
        ValueError
            If ``app_name`` is not configured.
        """
        if not self.app_name:
            msg = "app_name is required for Connect URL"
            raise ValueError(msg)

        meta_info_jwt = self._sign_meta_info_jwt()

        from urllib.parse import urlencode

        params = urlencode(
            {
                "app_name": self.app_name,
                "meta_info": meta_info_jwt,
                "callback_url": callback_url,
            }
        )
        return f"{frontend_url.rstrip('/')}/connect?{params}"


__all__ = ["Hezor2APIClient"]
