"""
Qdrant 向量存储实现

使用 Qdrant 作为向量数据库后端，通过 qdrant-client 进行交互。
每个知识库对应一个 Qdrant collection。
"""
import logging
import uuid
from typing import Any, Dict, List, Optional

from qdrant_client import AsyncQdrantClient
from qdrant_client.models import (
    Distance,
    FieldCondition,
    Filter,
    FilterSelector,
    MatchValue,
    PointIdsList,
    PointStruct,
    VectorParams,
)

from ai_platform.knowledge.vector_store.base import (
    BaseVectorStore,
    VectorPoint,
    VectorSearchResult,
)

logger = logging.getLogger(__name__)


class QdrantVectorStore(BaseVectorStore):
    """
    Qdrant 向量存储
    
    特性：
    - 高性能向量检索（HNSW 索引）
    - 支持 payload 过滤
    - 支持 REST 和 gRPC 协议
    - 与业务数据库完全解耦
    """

    def __init__(
        self,
        host: str = "localhost",
        port: int = 6333,
        api_key: Optional[str] = None,
        grpc_port: int = 6334,
        prefer_grpc: bool = False,
    ):
        self._host = host
        self._port = port
        self._api_key = api_key
        self._grpc_port = grpc_port
        self._prefer_grpc = prefer_grpc
        self._client: Optional[AsyncQdrantClient] = None

    async def _get_client(self) -> AsyncQdrantClient:
        """获取或创建 Qdrant 客户端（懒初始化）"""
        if self._client is None:
            # 如果 host 已包含协议前缀，直接作为 url 使用；
            # 否则拼接 http:// 避免 qdrant-client 对非 localhost 域名自动走 HTTPS
            if self._host.startswith("http://") or self._host.startswith("https://"):
                url = f"{self._host}:{self._port}"
            else:
                url = f"http://{self._host}:{self._port}"
            self._client = AsyncQdrantClient(
                url=url,
                api_key=self._api_key,
                grpc_port=self._grpc_port,
                prefer_grpc=self._prefer_grpc,
                timeout=30,
            )
        return self._client

    async def ensure_collection(
        self,
        knowledge_base_id: str,
        vector_size: int,
    ) -> None:
        """确保 collection 存在且维度匹配"""
        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)

        collections = await client.get_collections()
        existing_names = {c.name for c in collections.collections}

        if name in existing_names:
            # 检查已有 collection 的维度是否匹配
            info = await client.get_collection(collection_name=name)
            existing_size = info.config.params.vectors.size
            if existing_size != vector_size:
                logger.warning(
                    f"Qdrant collection {name} 维度不匹配: "
                    f"已有={existing_size}, 期望={vector_size}，删除重建"
                )
                await client.delete_collection(collection_name=name)
            else:
                return

        await client.create_collection(
            collection_name=name,
            vectors_config=VectorParams(
                size=vector_size,
                distance=Distance.COSINE,
            ),
        )
        # 创建 payload 索引，加速过滤查询
        await client.create_payload_index(
            collection_name=name,
            field_name="document_id",
            field_schema="keyword",
        )
        logger.info(f"Qdrant collection 已创建: {name} (dim={vector_size})")

    async def delete_collection(self, knowledge_base_id: str) -> None:
        """删除 collection"""
        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)
        try:
            await client.delete_collection(collection_name=name)
            logger.info(f"Qdrant collection 已删除: {name}")
        except Exception as e:
            logger.warning(f"删除 Qdrant collection 失败: {name}, {e}")

    async def upsert(
        self,
        knowledge_base_id: str,
        points: List[VectorPoint],
    ) -> None:
        """批量写入/更新向量"""
        if not points:
            return

        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)

        qdrant_points = [
            PointStruct(
                id=self._to_uuid(p.id),
                vector=p.vector,
                payload={**p.payload, 'segment_id': p.id},
            )
            for p in points
        ]

        # Qdrant 单次 upsert 建议不超过 100 个点
        batch_size = 100
        for i in range(0, len(qdrant_points), batch_size):
            batch = qdrant_points[i:i + batch_size]
            await client.upsert(
                collection_name=name,
                points=batch,
            )

    async def delete(
        self,
        knowledge_base_id: str,
        point_ids: List[str],
    ) -> None:
        """批量删除向量"""
        if not point_ids:
            return

        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)

        uuid_ids = [self._to_uuid(pid) for pid in point_ids]
        await client.delete(
            collection_name=name,
            points_selector=PointIdsList(points=uuid_ids),
        )

    async def search(
        self,
        knowledge_base_id: str,
        query_vector: List[float],
        top_k: int = 10,
        score_threshold: float = 0.0,
        filter_conditions: Optional[Dict[str, Any]] = None,
    ) -> List[VectorSearchResult]:
        """向量相似度搜索"""
        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)

        # 构建过滤条件
        query_filter = self._build_filter(filter_conditions) if filter_conditions else None

        try:
            results = await client.search(
                collection_name=name,
                query_vector=query_vector,
                limit=top_k,
                score_threshold=score_threshold,
                query_filter=query_filter,
                with_payload=True,
            )
        except Exception as e:
            logger.error(f"Qdrant 搜索失败: {e}")
            return []

        return [
            VectorSearchResult(
                id=(hit.payload or {}).get('segment_id', str(hit.id)),
                score=hit.score,
                payload=hit.payload or {},
            )
            for hit in results
        ]

    async def delete_by_filter(
        self,
        knowledge_base_id: str,
        filter_conditions: Dict[str, Any],
    ) -> None:
        """按条件删除向量"""
        client = await self._get_client()
        name = self.collection_name(knowledge_base_id)

        query_filter = self._build_filter(filter_conditions)
        if query_filter:
            await client.delete(
                collection_name=name,
                points_selector=FilterSelector(filter=query_filter),
            )

    async def health_check(self) -> bool:
        """健康检查"""
        try:
            client = await self._get_client()
            await client.get_collections()
            return True
        except Exception as e:
            logger.error(f"Qdrant 健康检查失败: {e}")
            return False

    @staticmethod
    def _to_uuid(string_id: str) -> str:
        """将任意字符串 ID 确定性转换为 UUID5（Qdrant 要求 point ID 为 UUID 或整数）"""
        return str(uuid.uuid5(uuid.NAMESPACE_DNS, string_id))

    @staticmethod
    def _build_filter(conditions: Dict[str, Any]) -> Optional[Filter]:
        """构建 Qdrant 过滤条件"""
        if not conditions:
            return None

        must = []
        for key, value in conditions.items():
            if isinstance(value, list):
                # 列表值：任一匹配（OR 语义），用 should 包裹后作为一个 must 条件
                should_conditions = [
                    FieldCondition(key=key, match=MatchValue(value=v))
                    for v in value
                ]
                must.append(Filter(should=should_conditions))
            else:
                must.append(FieldCondition(key=key, match=MatchValue(value=value)))

        return Filter(must=must) if must else None
