"""Hezor2 SDK - 统一的 SDK 接口。

提供便捷的上下文管理器接口，集成所有 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 import (
    CreationGenerateResult,
    CreationGenerateResultV2,
)
from hezor_common.data_model.creations.execution.webhook import (
    PublicReportsResponseData,
    PublishCreationResponseData,
    ReportMetadata,
)
from hezor_common.data_model.searching import DataRetrieveResult, KnowledgeSearchResult
from hezor_common.data_model.searching.data.graph_models import GraphQueryResult
from hezor_common.data_model.user_app_binding import AppCertInfo, UserAppBindingInfo
from hezor_common.data_model.webhook import WebhookActionHelp
from hezor_common.transfer.base_sdk import MetaInfo
from hezor_common.transfer.hezor2_sdk.base.constants import (
    DEFAULT_API_BASE_URL,
    DEFAULT_API_KEY,
)
from hezor_common.transfer.hezor2_sdk.base.hezor2_api_client import (
    Hezor2APIClient,
)

logger = logging.getLogger(__name__)


class Hezor2SDK:
    """Hezor2 SDK 统一接口。

    提供便捷的上下文管理器接口，集成创建报告发布等功能。

    Parameters
    ----------
    base_url : str
        API 基础 URL，默认使用 DEFAULT_API_BASE_URL
    timeout : float
        请求超时时间（秒）
    api_key : str | None
        API 密钥，用于认证
    meta_info : MetaInfo | None
        元信息对象，用于签名认证
    private_key_path : str | Path | None
        私钥文件路径，用于签名认证（与 meta_info 配合使用）
    password : bytes | None
        私钥密码
    meta_info_expires_in : int
        MetaInfo JWT token 过期时间（秒）

    Examples
    --------
    >>> # 基本使用
    >>> async with Hezor2SDK() as sdk:
    ...     result = await sdk.publish_creation_report(
    ...         creation_result=creation_result,
    ...         task_id="monthly_report",
    ...         execution_id="exec_2026_01_27_001"
    ...     )

    >>> # 使用认证
    >>> async with Hezor2SDK(api_key="your-key") as sdk:
    ...     result = await sdk.publish_creation_report(...)
    """

    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,
        private_key_pem: str | bytes | None = None,
        password: bytes | None = None,
        meta_info_expires_in: int = 3600,
        app_name: str | None = None,
    ):
        """初始化 Hezor2 SDK。

        Parameters
        ----------
        base_url : str
            API 基础 URL
        timeout : float
            请求超时时间（秒）
        api_key : str | None
            API 密钥
        meta_info : MetaInfo | None
            元信息对象
        private_key_path : str | Path | None
            私钥文件路径
        private_key_pem : str | bytes | None
            PEM 编码的私钥内容
        password : bytes | None
            私钥密码
        meta_info_expires_in : int
            MetaInfo JWT token 过期时间（秒）
        app_name : str | None
            应用名称，通过 X-APP-NAME 头传递给服务端
        """
        self._client = Hezor2APIClient(
            base_url=base_url,
            timeout=timeout,
            api_key=api_key,
            meta_info=meta_info,
            private_key_path=private_key_path,
            private_key_pem=private_key_pem,
            password=password,
            meta_info_expires_in=meta_info_expires_in,
            app_name=app_name,
        )

    async def __aenter__(self) -> "Hezor2SDK":
        """进入异步上下文管理器。

        Returns
        -------
        Hezor2SDK
            SDK 实例
        """
        await self._client.__aenter__()
        logger.debug("Hezor2SDK context manager entered")
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
        """退出异步上下文管理器。

        Parameters
        ----------
        exc_type : type | None
            异常类型
        exc_val : Exception | None
            异常值
        exc_tb : TracebackType | None
            异常 traceback
        """
        await self._client.__aexit__(exc_type, exc_val, exc_tb)
        logger.debug("Hezor2SDK context manager exited")

    async def generate_report_id(
        self,
        count: int = 1,
    ) -> list[str]:
        """生成唯一报告 ID。

        Parameters
        ----------
        count : int
            需要生成的 ID 数量（the default is 1）。

        Returns
        -------
        list[str]
            生成的报告 ID 列表，带 ``rpt_`` 前缀。

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     ids = await sdk.generate_report_id(count=3)
        ...     print(ids)
        ['rpt_xxx', 'rpt_yyy', 'rpt_zzz']
        """
        return await self._client.generate_report_id(count=count)

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

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

        Parameters
        ----------
        creation_result : CreationGenerateResult or CreationGenerateResultV2
            生成的创建结果。推荐使用
            ``CreationGenerateResultV2``。
        task_id : str or None
            任务 ID。为 None 时服务端自动生成。
        execution_id : str or None
            执行 ID。为 None 时服务端自动生成。

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

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。

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

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

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

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

        旧用法（将废弃）：

        >>> async with Hezor2SDK() as sdk:
        ...     response = await sdk.publish_creation_report(
        ...         creation_result=CreationGenerateResult(...),
        ...     )
        """
        return await self._client.publish_creation_report(
            payload=creation_result,
            task_id=task_id,
            execution_id=execution_id,
        )

    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 Hezor2SDK() as sdk:
        ...     resp = await sdk.get_report_status(
        ...         creation_id="crt_xxx",
        ...         report_id="rpt_xxx",
        ...     )
        ...     print(resp.report_title)
        """
        return await self._client.get_report_status(
            creation_id=creation_id,
            report_id=report_id,
        )

    async def get_public_reports(
        self,
        top_n: int = 5,
        creation_id: str | None = None,
    ) -> PublicReportsResponseData:
        """获取公开报告列表。

        调用 ``get_public_reports`` webhook，该 action 支持
        匿名访问（无需 API Key）。

        Parameters
        ----------
        top_n : int
            返回报告数量上限（the default is 5）。
        creation_id : str or None
            指定 Creation ID 仅获取该 Creation 的公开报告。
            为 None 时获取所有公开 Creation 的报告。

        Returns
        -------
        PublicReportsResponseData
            包含 ``items``（报告列表）和 ``total``（总数）。

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

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     result = await sdk.get_public_reports(top_n=10)
        ...     for item in result.items:
        ...         print(item.report_title)
        """
        return await self._client.get_public_reports(
            top_n=top_n,
            creation_id=creation_id,
        )

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

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

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

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     is_healthy, health_data = await sdk.health_check()
        ...     if is_healthy:
        ...         print("Service is healthy")
        ...     print(f"Details: {health_data}")
        """
        try:
            return await self._client.health_check()
        except httpx.HTTPError:
            # HTTP 请求失败，服务不健康
            return False, {"error": "HTTP request failed"}

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

        获取指定 action 的文档信息，包括 payload schema、
        response data schema 和用法示例。

        Parameters
        ----------
        action : str
            要查询的 action 名称，如
            ``"generate_report_id"``、
            ``"publish_creation_report"``、
            ``"get_report_status"``。

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

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     info = await sdk.webhook_help(
        ...         "generate_report_id"
        ...     )
        ...     print(info.action)
        'generate_report_id'
        """
        return await self._client.webhook_help(action)

    async def knowledge_retrieve(
        self,
        query: str,
        *,
        top_k: int = 3,
        score_threshold: float = 0.5,
    ) -> KnowledgeSearchResult:
        """调用 ``knowledge_retrieve`` webhook 并返回结构化结果。

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

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

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.knowledge_retrieve(
            query=query,
            top_k=top_k,
            score_threshold=score_threshold,
        )

    async def data_retrieve(
        self,
        query: str,
        *,
        top_k: int = 1,
    ) -> DataRetrieveResult:
        """调用 ``data_retrieve`` webhook 并返回结构化结果。

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

        Returns
        -------
        DataRetrieveResult
            各工具执行结果映射，包含 ``query`` 和 ``results``。

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.data_retrieve(
            query=query,
            top_k=top_k,
        )

    async def knowledge_search(
        self,
        query: str,
        collection: str,
        *,
        top_k: int = 5,
        score_threshold: float = 0.5,
        metadata_filter: dict[str, Any] | None = None,
        date_range: list[str] | None = None,
        search_mode: str = "semantic",
        vector_weight: float = 0.7,
        text_weight: float = 0.3,
        entity_type: str | None = None,
        doc_id: str | None = None,
    ) -> KnowledgeSearchResult:
        """调用 ``knowledge_search`` webhook 执行单集合精确检索。

        Parameters
        ----------
        query : str
            检索查询文本。
        collection : str
            目标集合名称（``"chunks"``、``"entities"``、
            ``"communities"``、``"pictures"``、
            ``"relationships"``）。
        top_k : int
            返回数量上限。
        score_threshold : float
            相似度阈值。
        metadata_filter : dict[str, Any] | None
            元数据过滤条件。
        date_range : list[str] | None
            日期范围 ``["YYYY-MM-DD", "YYYY-MM-DD"]``。
        search_mode : str
            检索模式（``"semantic"`` 或 ``"hybrid"``，
            仅 chunks 支持 hybrid）。
        vector_weight : float
            混合检索向量权重（仅 hybrid 模式）。
        text_weight : float
            混合检索全文权重（仅 hybrid 模式）。
        entity_type : str | None
            实体类型筛选（仅 entities 集合有效）。
        doc_id : str | None
            文档 ID 过滤（仅 chunks/pictures 集合有效）。

        Returns
        -------
        KnowledgeSearchResult
            单集合检索结果。

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.knowledge_search(
            query=query,
            collection=collection,
            top_k=top_k,
            score_threshold=score_threshold,
            metadata_filter=metadata_filter,
            date_range=date_range,
            search_mode=search_mode,
            vector_weight=vector_weight,
            text_weight=text_weight,
            entity_type=entity_type,
            doc_id=doc_id,
        )

    async def knowledge_graph_query(
        self,
        query_type: str,
        *,
        keyword: str | None = None,
        entity_name: str | None = None,
        entity_type: str | None = None,
        relationship_type: str | None = None,
        direction: str = "both",
        target_name: str | None = None,
        max_depth: int = 2,
        max_paths: int = 3,
        community_id: str | None = None,
        limit: int = 20,
    ) -> GraphQueryResult:
        """调用 ``knowledge_graph_query`` webhook 执行图谱拓扑查询。

        Parameters
        ----------
        query_type : str
            查询类型（``"graph_statistics"``、
            ``"entity_search"``、
            ``"entity_relationships"``、
            ``"entity_subgraph"``、``"find_paths"``、
            ``"entity_co_occurrence"``、
            ``"entity_communities"``、
            ``"community_subgraph"``、
            ``"related_communities"``）。
        keyword : str | None
            搜索关键词（entity_search）。
        entity_name : str | None
            实体名称。
        entity_type : str | None
            实体类型筛选。
        relationship_type : str | None
            关系类型筛选。
        direction : str
            关系方向（``"in"``、``"out"``、``"both"``）。
        target_name : str | None
            目标实体名称（find_paths）。
        max_depth : int
            最大深度。
        max_paths : int
            最大路径数（find_paths）。
        community_id : str | None
            社区 ID。
        limit : int
            返回数量上限。

        Returns
        -------
        GraphQueryResult
            统一图查询结果。

        Raises
        ------
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.knowledge_graph_query(
            query_type=query_type,
            keyword=keyword,
            entity_name=entity_name,
            entity_type=entity_type,
            relationship_type=relationship_type,
            direction=direction,
            target_name=target_name,
            max_depth=max_depth,
            max_paths=max_paths,
            community_id=community_id,
            limit=limit,
        )

    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
        -------
        PullConfigsResponse
            包含 ``public``（公共配置）和 ``user``（用户私有配置）。
            调用 ``.merged()`` 获得合并字典（user 优先）。

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

        Examples
        --------
        拉取全量配置并注入环境变量：

        >>> import os
        >>> async with Hezor2SDK() as sdk:
        ...     data = await sdk.pull_configs()
        ...     for key, value in data.merged().items():
        ...         os.environ[key] = value

        只拉取指定 key：

        >>> async with Hezor2SDK() as sdk:
        ...     data = await sdk.pull_configs(
        ...         keys=["DATABASE_URL", "REDIS_HOST"]
        ...     )
        ...     print(data.merged())

        自定义域名场景（覆盖插值 URL）：

        >>> async with Hezor2SDK() as sdk:
        ...     data = await sdk.pull_configs(
        ...         global_base_url="https://custom-domain.example.com"
        ...     )
        """
        return await self._client.pull_configs(
            keys=keys,
            global_base_url=global_base_url,
        )

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

    async def connect_verify(self, callback_url: str) -> ConnectVerifyResponse:
        """验证 Connect 应用身份。

        Parameters
        ----------
        callback_url : str
            要验证的回调 URL（必须在应用的 redirect_uris 中）。

        Returns
        -------
        ConnectVerifyResponse
            验证结果。

        Raises
        ------
        ValueError
            如果 ``app_name`` 未配置。
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.connect_verify(callback_url)

    async def connect_refresh(self, refresh_token: str) -> ConnectRefreshResponse:
        """刷新 Connect 令牌。

        Parameters
        ----------
        refresh_token : str
            从 Connect 登录回调中获得的 refresh token。

        Returns
        -------
        ConnectRefreshResponse
            新的令牌和用户信息。

        Raises
        ------
        ValueError
            如果 ``app_name`` 未配置。
        httpx.HTTPError
            如果 HTTP 请求失败。
        """
        return await self._client.connect_refresh(refresh_token)

    def build_connect_url(self, frontend_url: str, callback_url: str) -> str:
        """构建 Connect 登录 URL。

        签名 MetaInfo JWT 并构建完整的 URL，用户应被重定向到该 URL
        以开始 Connect 登录流程。

        Parameters
        ----------
        frontend_url : str
            Hezor 前端基础 URL（如 ``"https://your-hezor-domain.com"``）。
        callback_url : str
            登录完成后重定向的回调 URL。

        Returns
        -------
        str
            带查询参数的完整 Connect 登录 URL。

        Raises
        ------
        ValueError
            如果 ``app_name`` 未配置。
        """
        return self._client.build_connect_url(frontend_url, callback_url)

    # ── App certs ───────────────────────────────────────────────────────

    @classmethod
    async def fetch_app_certs(
        cls,
        app_name: str,
        *,
        base_url: str = DEFAULT_API_BASE_URL,
        api_key: str | None = DEFAULT_API_KEY,
    ) -> AppCertInfo:
        """获取应用凭证（类方法，无需创建实例）。

        仅需 ``base_url``、``api_key`` 和 ``app_name``，
        无需签名配置。返回的 ``cert_content``（私钥）和
        ``client_secret``（密码）可直接用于初始化带签名的
        SDK 实例。

        Parameters
        ----------
        app_name : str
            Casdoor 应用名称
        base_url : str
            API 基础 URL
        api_key : str | None
            API 密钥

        Returns
        -------
        AppCertInfo
            应用凭证信息（client_id, client_secret, cert_content）

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

        Examples
        --------
        >>> cert = await Hezor2SDK.fetch_app_certs(
        ...     "my-app",
        ...     api_key="your-key",
        ... )
        >>> print(cert.client_id)
        """
        async with cls(
            base_url=base_url,
            api_key=api_key,
            app_name=app_name,
        ) as sdk:
            return await sdk.get_app_certs(app_name)

    async def get_app_certs(self, app_name: str) -> AppCertInfo:
        """获取应用凭证。

        通过 API Key 认证，获取指定应用的 client_id、client_secret 和证书。
        需要当前用户已绑定该应用且处于启用状态。

        Parameters
        ----------
        app_name : str
            Casdoor 应用名称

        Returns
        -------
        AppCertInfo
            应用凭证信息（client_id, client_secret, cert_content）

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

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     cert = await sdk.get_app_certs("my-app")
        ...     print(cert.client_id)
        """

        return await self._client.get_app_certs(app_name)

    # ── My apps ─────────────────────────────────────────────────────────

    async def get_my_apps(self) -> list[UserAppBindingInfo]:
        """获取当前用户绑定的应用列表（含凭证）。

        通过 JWT 认证，获取当前用户所有绑定的应用及其
        凭证信息（client_id、client_secret、证书等）。

        Returns
        -------
        list[UserAppBindingInfo]
            用户绑定的应用列表，每项包含凭证信息。

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

        Examples
        --------
        >>> async with Hezor2SDK() as sdk:
        ...     apps = await sdk.get_my_apps()
        ...     for app in apps:
        ...         print(app.app_name, app.client_id)
        """
        return await self._client.get_my_apps()


__all__ = ["Hezor2SDK"]
