"""
weaviate.py
-----------
Weaviate implementation of VectorBackend.
"""

from typing import Any, Dict, List, Optional, Sequence, Tuple
import numpy as np
import uuid

try:
    import weaviate
except ImportError:
    weaviate = None

from hieromem_core.backends.interface import VectorBackend


class WeaviateBackend(VectorBackend):
    def __init__(self, url: str = None, api_key: str = None):
        if not weaviate:
            raise ImportError("weaviate-client is required for WeaviateBackend")

        auth_config = weaviate.AuthApiKey(api_key=api_key) if api_key else None
        self.client = weaviate.Client(
            url=url or "http://localhost:8080", auth_client_secret=auth_config
        )
        self.class_name = "Episode"
        self._ensure_schema()

    def _ensure_schema(self):
        if not self.client.schema.exists(self.class_name):
            class_obj = {
                "class": self.class_name,
                "vectorizer": "none",  # We provide vectors
                "properties": [
                    {"name": "external_id", "dataType": ["string"]},
                    {"name": "content", "dataType": ["text"]},
                    # Dynamic metadata support is tricky in strict schemas,
                    # usually mapped to specific props or a json dump.
                    # Weaviate supports arbitrary props if configured?
                    # Let's assume metadata keys are consistent or flatten them.
                ],
            }
            self.client.schema.create_class(class_obj)

    def add(
        self,
        ids: Sequence[str],
        embeddings: np.ndarray,
        metadata: Optional[List[Dict[str, Any]]] = None,
    ) -> None:
        with self.client.batch as batch:
            batch.batch_size = 100
            for i, eid in enumerate(ids):
                meta = metadata[i] if metadata else {}
                # We put metadata directly as properties
                props = {**meta, "external_id": eid}

                # Weaviate uses UUIDs for objects. We can use deterministic UUID from external_id
                uuid_val = str(uuid.uuid5(uuid.NAMESPACE_DNS, eid))

                batch.add_data_object(
                    data_object=props,
                    class_name=self.class_name,
                    uuid=uuid_val,
                    vector=embeddings[i],
                )

    def search(
        self,
        query_embedding: np.ndarray,
        k: int = 10,
        filter: Optional[Dict[str, Any]] = None,
    ) -> List[Tuple[str, float, Dict[str, Any]]]:

        query = (
            self.client.query.get(
                self.class_name, ["external_id", "_additional { distance }"]
            )
            .with_near_vector({"vector": query_embedding.tolist()})
            .with_limit(k)
        )

        if filter:
            # Weaviate filter syntax construction
            operands = []
            for key, val in filter.items():
                operands.append(
                    {
                        "path": [key],
                        "operator": "Equal",
                        "valueString": str(val),  # simplification
                    }
                )

            if len(operands) > 1:
                where_filter = {"operator": "And", "operands": operands}
            elif len(operands) == 1:
                where_filter = operands[0]
            else:
                where_filter = None

            if where_filter:
                query = query.with_where(where_filter)

        response = query.do()

        results = []
        if "data" in response and "Get" in response["data"]:
            items = response["data"]["Get"].get(self.class_name, [])
            for item in items:
                eid = item.get("external_id")
                dist = item.get("_additional", {}).get("distance")
                # Retrieve full metadata? logic above only fetched external_id.
                # To get all props, we need to list them in .get().
                # For now returning empty meta or we fetch all known props.
                results.append(
                    (eid, dist, {})
                )  # Metadata implementation pending full schema strategy

        return results

    def get(
        self, ids: Sequence[str]
    ) -> List[Optional[Tuple[str, np.ndarray, Dict[str, Any]]]]:
        # Weaviate fetching by ID
        # Since we mapped external_id to UUID, we can fetch if we reconstruct the UUID
        results = []
        for eid in ids:
            uuid_val = str(uuid.uuid5(uuid.NAMESPACE_DNS, eid))
            obj = self.client.data_object.get_by_id(
                uuid_val, class_name=self.class_name, with_vector=True
            )
            if obj:
                vec = np.array(obj.get("vector", []))
                props = obj.get("properties", {})
                results.append((eid, vec, props))
            else:
                results.append(None)
        return results

    def delete(self, ids: Sequence[str]) -> None:
        # Batch delete by ID not easily supported in old client?
        # New v4 client recommended. Using v3 assumed here.
        # Deleting by query usually.
        for eid in ids:
            self.client.batch.delete_objects(
                class_name=self.class_name,
                where={
                    "path": ["external_id"],
                    "operator": "Equal",
                    "valueString": eid,
                },
            )

    def count(self) -> int:
        res = self.client.query.aggregate(self.class_name).with_meta_count().do()
        try:
            return res["data"]["Aggregate"][self.class_name][0]["meta"]["count"]
        except Exception:
            return 0

    def clear(self) -> None:
        self.client.schema.delete_class(self.class_name)
        self._ensure_schema()
