"""Qdrant vector store helpers for DSCons knowledge retrieval."""

from __future__ import annotations

import uuid
from typing import Any

from qdrant_client import QdrantClient
from qdrant_client.http import models as qdrant_models

from app.core.settings import Settings
from app.modules.core.application.embeddings import EmbeddingService


class QdrantStore:
    """Small wrapper around qdrant-client for collection, upsert, and search."""

    def __init__(self, settings: Settings, embedding_service: EmbeddingService) -> None:
        """Initialize the store client and collection metadata."""
        self._settings = settings
        self._embedding_service = embedding_service
        self._client = QdrantClient(url=settings.qdrant_url)
        self._collection_name = settings.qdrant_collection

    @property
    def collection_name(self) -> str:
        """Return the configured collection name."""
        return self._collection_name

    def ensure_collection(self) -> None:
        """Create the configured collection if it does not already exist."""
        collections = self._client.get_collections().collections
        target_size = self._embedding_service.vector_size

        for col in collections:
            if col.name == self._collection_name:
                try:
                    info = self._client.get_collection(
                        collection_name=self._collection_name
                    )
                    if info.config.params.vectors.size == target_size:
                        return
                    self._client.delete_collection(
                        collection_name=self._collection_name
                    )
                except Exception:
                    pass

        self._client.create_collection(
            collection_name=self._collection_name,
            vectors_config=qdrant_models.VectorParams(
                size=target_size,
                distance=qdrant_models.Distance.COSINE,
            ),
        )

    def upsert_texts(
        self,
        texts: list[str],
        *,
        metadatas: list[dict[str, Any]] | None = None,
        ids: list[str] | None = None,
    ) -> int:
        """Embed and upsert texts into Qdrant."""
        if not texts:
            return 0

        self.ensure_collection()
        vectors = self._embedding_service.embed_texts(texts)
        payloads = metadatas or [{} for _ in texts]
        point_ids = ids or [str(uuid.uuid4()) for _ in texts]

        points = [
            qdrant_models.PointStruct(
                id=point_ids[index],
                vector=vectors[index],
                payload={"text": texts[index], "metadata": payloads[index]},
            )
            for index in range(len(texts))
        ]
        self._client.upsert(collection_name=self._collection_name, points=points)
        return len(points)

    async def upsert(self, points: list[dict[str, Any]]) -> int:
        """Upsert pre-built points into Qdrant."""
        if not points:
            return 0

        self.ensure_collection()
        qdrant_points = [
            qdrant_models.PointStruct(
                id=point.get("id", str(uuid.uuid4())),
                vector=point["vector"],
                payload=point.get("payload", {}),
            )
            for point in points
        ]
        self._client.upsert(collection_name=self._collection_name, points=qdrant_points)
        return len(qdrant_points)

    def search(
        self,
        query: str | None = None,
        *,
        query_vector: list[float] | None = None,
        limit: int = 5,
        filters: dict[str, Any] | None = None,
    ) -> list[dict[str, Any]]:
        """Search the collection for chunks related to the query."""
        self.ensure_collection()

        vector = query_vector
        if vector is None:
            if query is None:
                raise ValueError("Either query or query_vector must be provided.")
            vector = self._embedding_service.embed_query(query)

        query_filter = self._build_query_filter(filters)

        query_points = getattr(self._client, "query_points", None)
        if callable(query_points):
            response = query_points(
                collection_name=self._collection_name,
                query=vector,
                limit=limit,
                with_payload=True,
                query_filter=query_filter,
            )
            results = getattr(response, "points", response)
        else:
            search_method = getattr(self._client, "search", None)
            if not callable(search_method):
                raise AttributeError(
                    "Qdrant client does not support query_points or search."
                )
            results = search_method(
                collection_name=self._collection_name,
                query_vector=vector,
                limit=limit,
                with_payload=True,
                query_filter=query_filter,
            )

        items: list[dict[str, Any]] = []
        for result in results:
            payload = result.payload or {}
            items.append(
                {
                    "id": str(result.id),
                    "score": float(result.score),
                    "payload": payload,
                    "text": payload.get("text", ""),
                    "metadata": payload.get("metadata", {}) or {},
                }
            )
        return items

    def scroll(
        self,
        *,
        limit: int = 100,
        filters: dict[str, Any] | None = None,
    ) -> list[dict[str, Any]]:
        """Return points by metadata filter without semantic ranking."""
        self.ensure_collection()
        query_filter = self._build_query_filter(filters)
        offset = None
        items: list[dict[str, Any]] = []

        while True:
            records, offset = self._client.scroll(
                collection_name=self._collection_name,
                scroll_filter=query_filter,
                with_payload=True,
                with_vectors=False,
                limit=limit,
                offset=offset,
            )
            for record in records:
                payload = record.payload or {}
                items.append(
                    {
                        "id": str(record.id),
                        "score": 0.0,
                        "payload": payload,
                        "text": payload.get("text", ""),
                        "metadata": payload.get("metadata", {}) or {},
                    }
                )
            if offset is None:
                break

        return items

    @staticmethod
    def _build_query_filter(
        filters: dict[str, Any] | None,
    ) -> qdrant_models.Filter | None:
        """Build a Qdrant metadata filter from plain key/value pairs."""
        if not filters:
            return None

        return qdrant_models.Filter(
            must=[
                qdrant_models.FieldCondition(
                    key=f"metadata.{key}",
                    match=qdrant_models.MatchValue(value=value),
                )
                for key, value in filters.items()
            ]
        )
