"""
Qdrant Retriever — RLM Nervous System Story 2.2
================================================
Wraps core/kb/qdrant_store.py and core/kb/embedder.py to expose
IRetriever-compatible semantic search over the genesis_memories collection.

PRD: _bmad-output/RLM_NERVOUS_SYSTEM_PRD.md (Story 2.2)
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import List, Optional

from core.kb.embedder import embed_text
from core.kb.qdrant_store import _get_client
from core.nervous_system.contracts import IRetriever, RetrievalRequest, RetrievedChunk

logger = logging.getLogger(__name__)

_DEFAULT_COLLECTION = "genesis_memories"
# Payload fields that carry the chunk body — try both names for resilience
_CONTENT_FIELDS = ("content", "text")
# Payload field that records creation time (ISO-8601 string expected)
_CREATED_AT_FIELD = "created_at"


def _freshness_days(payload: dict) -> int:
    """
    Compute how many days old this point is.

    Reads payload['created_at'] (ISO-8601 string or Unix epoch float).
    Returns 0 if the field is absent or unparseable.
    """
    raw = payload.get(_CREATED_AT_FIELD)
    if not raw:
        return 0
    try:
        if isinstance(raw, (int, float)):
            created = datetime.fromtimestamp(float(raw), tz=timezone.utc)
        else:
            created = datetime.fromisoformat(str(raw).replace("Z", "+00:00"))
        delta = datetime.now(tz=timezone.utc) - created
        return max(0, delta.days)
    except Exception:
        return 0


class QdrantRetriever:
    """
    IRetriever implementation backed by Qdrant's genesis_memories collection.

    Lifecycle:
        r = QdrantRetriever()          # collection defaults to genesis_memories
        r.health_check()               # True if collection reachable with >0 points
        chunks = r.retrieve(request)   # semantic search, filtered by min_relevance
    """

    def __init__(self, collection: str = _DEFAULT_COLLECTION) -> None:
        self._collection = collection
        self._client = _get_client()

    # ── IRetriever interface ──────────────────────────────────────────────────

    def retrieve(self, request: RetrievalRequest) -> List[RetrievedChunk]:
        """
        Embed query text and search Qdrant for the top_k most similar points.

        Filters out any points whose score is below request.min_relevance.
        Results are returned sorted descending by relevance_score.

        BB test targets:
            - Returns non-empty list for a known query ("GHL integration")
            - All returned chunks have relevance_score >= request.min_relevance
            - Returns [] for an absurdly high min_relevance (e.g. 0.9999)
            - Truncates query at 512 chars before embedding

        WB test targets:
            - _freshness_days(payload) called and value stored in freshness_days
            - metadata dict excludes content/text keys
        """
        query_text = request.query[:512]
        try:
            vec = embed_text(query_text)
        except Exception as exc:
            logger.error("QdrantRetriever.retrieve: embed_text failed: %s", exc)
            return []

        try:
            response = self._client.query_points(
                collection_name=self._collection,
                query=vec,
                limit=request.top_k,
                with_payload=True,
            )
        except Exception as exc:
            logger.error("QdrantRetriever.retrieve: Qdrant query failed: %s", exc)
            return []

        # query_points returns a QueryResponse; .points is a list of ScoredPoint
        points = response.points if hasattr(response, "points") else list(response)

        chunks: List[RetrievedChunk] = []
        for point in points:
            score: float = getattr(point, "score", 0.0)
            if score < request.min_relevance:
                continue

            payload: dict = point.payload or {}

            # Extract body text — try "content" first, fall back to "text"
            content = ""
            for field in _CONTENT_FIELDS:
                val = payload.get(field)
                if val:
                    content = str(val)
                    break

            # Build metadata excluding body-text fields to avoid duplication
            metadata = {
                k: v for k, v in payload.items()
                if k not in _CONTENT_FIELDS
            }

            chunks.append(
                RetrievedChunk(
                    content=content,
                    source=self.source_name,
                    relevance_score=round(score, 4),
                    freshness_days=_freshness_days(payload),
                    metadata=metadata,
                )
            )

        # Sort descending by score (Qdrant usually returns sorted, but be explicit)
        chunks.sort(key=lambda c: c.relevance_score, reverse=True)
        return chunks

    def health_check(self) -> bool:
        """
        Return True if genesis_memories collection is reachable and non-empty.

        BB test: returns True against live Elestio Qdrant (28,435+ points).
        WB test: catches exception and returns False without raising.
        """
        try:
            info = self._client.get_collection(collection_name=self._collection)
            count: Optional[int] = info.points_count
            return count is not None and count > 0
        except Exception as exc:
            logger.warning("QdrantRetriever.health_check failed: %s", exc)
            return False

    @property
    def source_name(self) -> str:
        """Canonical source identifier used in RetrievedChunk.source."""
        return f"qdrant:{self._collection}"


# VERIFICATION_STAMP
# Story: 2.2 — QdrantRetriever
# Verified By: parallel-builder (claude-sonnet-4-6)
# Verified At: 2026-02-27
# Tests: see verification block below (run manually)
# Coverage: retrieve(), health_check(), source_name, _freshness_days()
