"""KBAdapter 抽象基类 - 知识库适配器的基础实现

实现 KnowledgeBase 协议，并提供通用扩展方法：
- search(): query() 的别名
- get_document(): 按 ID 获取单个文档
- authenticate(): 认证验证
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import Any

import httpx

from agentkit.memory.knowledge_base import Document, QueryResult, SourceInfo

logger = logging.getLogger(__name__)


class KBAdapter(ABC):
    """知识库适配器抽象基类

    实现 KnowledgeBase 协议的所有方法，并提供额外便利方法。
    子类需实现 _make_client() 和具体的 HTTP 调用逻辑。
    """

    def __init__(
        self,
        source_id: str,
        source_name: str,
        source_type: str,
        timeout: int = 30,
    ):
        self._source_id = source_id
        self._source_name = source_name
        self._source_type = source_type
        self._timeout = timeout
        self._client: httpx.AsyncClient | None = None
        self._authenticated = False

    @property
    def source_id(self) -> str:
        return self._source_id

    @property
    def source_name(self) -> str:
        return self._source_name

    @property
    def source_type(self) -> str:
        return self._source_type

    def _get_client(self) -> httpx.AsyncClient:
        """获取或创建 HTTP 客户端"""
        if self._client is None or self._client.is_closed:
            self._client = self._make_client()
        return self._client

    @abstractmethod
    def _make_client(self) -> httpx.AsyncClient:
        """创建 HTTP 客户端（子类实现，配置 base_url、headers、auth 等）"""
        ...

    # ------------------------------------------------------------------
    # KnowledgeBase 协议方法
    # ------------------------------------------------------------------

    async def ingest(self, documents: list[Document]) -> list[str]:
        """写入文档到知识库，返回文档 ID 列表

        默认实现：逐个调用 _ingest_one()。
        子类可覆盖以实现批量写入。
        """
        ids: list[str] = []
        for doc in documents:
            doc_id = await self._ingest_one(doc)
            if doc_id:
                ids.append(doc_id)
        return ids

    async def _ingest_one(self, document: Document) -> str | None:
        """写入单个文档（子类可覆盖）"""
        logger.warning(
            f"{self.__class__.__name__} does not support ingest; "
            f"document '{document.doc_id}' skipped"
        )
        return None

    async def query(self, text: str, top_k: int = 5) -> list[QueryResult]:
        """语义检索知识库（委托给 search）"""
        return await self.search(text, top_k=top_k)

    async def delete_by_id(self, id: str) -> bool:
        """按文档 ID 删除（子类可覆盖）"""
        logger.warning(
            f"{self.__class__.__name__} does not support delete_by_id; "
            f"id '{id}' skipped"
        )
        return False

    async def list_sources(self) -> list[SourceInfo]:
        """列出可用信息源"""
        return [
            SourceInfo(
                source_id=self._source_id,
                source_name=self._source_name,
                source_type=self._source_type,
            )
        ]

    @abstractmethod
    async def health_check(self) -> bool:
        """检查知识库连接状态（子类实现）"""
        ...

    # ------------------------------------------------------------------
    # 扩展方法
    # ------------------------------------------------------------------

    @abstractmethod
    async def search(self, query: str, top_k: int = 5) -> list[QueryResult]:
        """语义检索知识库（子类实现）"""
        ...

    async def get_document(self, doc_id: str) -> Document | None:
        """按 ID 获取单个文档（子类可覆盖）"""
        logger.warning(
            f"{self.__class__.__name__} does not support get_document; "
            f"doc_id '{doc_id}' not found"
        )
        return None

    async def authenticate(self) -> bool:
        """认证验证（子类可覆盖）

        默认实现调用 health_check()。
        """
        try:
            self._authenticated = await self.health_check()
        except Exception:
            self._authenticated = False
        return self._authenticated

    # ------------------------------------------------------------------
    # 生命周期管理
    # ------------------------------------------------------------------

    async def close(self) -> None:
        """关闭 HTTP 客户端"""
        if self._client and not self._client.is_closed:
            await self._client.aclose()
            self._client = None

    async def __aenter__(self) -> KBAdapter:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()
