"""cortex/api/routes/memory/search.py — Semantic memory search endpoint."""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional

from fastapi import Depends, Request, status

from cortex.api.deps import PREFIX, _resolve_owner_id, _serialise, get_plugin
from cortex.api.models import (
    MemorySearchRequest,
    MemorySearchResponse,
)

logger = logging.getLogger(__name__)


def _register(router, verify_api_key):
    """Register search endpoints."""

    @router.post(
        f"{PREFIX}/memories/search",
        response_model=MemorySearchResponse,
        status_code=status.HTTP_200_OK,
        summary="Semantic memory search",
        tags=["memory"],
        operation_id="search_memories",
    )
    async def search_memories(
        request: Request,
        body: MemorySearchRequest,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> MemorySearchResponse:
        """Semantic search over stored memories."""
        errors: List[str] = []
        items: List[Dict[str, Any]] = []

        owner_id = _resolve_owner_id(request, body.owner_id)
        filters = body.filters or {}
        requested_limit = body.top_k if body.top_k is not None else body.limit

        try:
            result = await plugin.retrieve(
                query=body.query,
                token_budget=None,
                mode="auto",
                owner_id=owner_id,
            )

            if result is None:
                return MemorySearchResponse(
                    errors=["Retrieval pipeline not available"], total=0
                )

            raw_items = getattr(result, "items", []) or []

            category_filter = filters.get("category")
            status_filter = filters.get("status")
            entity_filter = filters.get("entity_name")

            # Auto-enable include_archived when status filter targets non-active
            effective_include_archived = body.include_archived
            if status_filter and status_filter != "active":
                effective_include_archived = True

            out: List[Dict[str, Any]] = []
            for item in raw_items:
                d = _serialise(item.__dict__) if hasattr(item, "__dict__") else _serialise(item)

                # Extract category from metadata when top-level is empty (#R-242)
                if not d.get("category"):
                    _meta = d.get("metadata") or {}
                    if isinstance(_meta, dict) and _meta.get("category"):
                        d["category"] = _meta["category"]

                # Normalize status: treat None/"" as "active" (#R-242)
                item_status = d.get("status") or "active"

                # Default status filter: exclude non-active unless include_archived
                if not effective_include_archived and item_status != "active":
                    continue

                if category_filter and d.get("category") != category_filter:
                    continue
                if status_filter and item_status != status_filter:
                    continue
                if entity_filter:
                    subject = (d.get("subject") or "").lower()
                    obj_val = (d.get("object_value") or "").lower()
                    if entity_filter.lower() not in subject and entity_filter.lower() not in obj_val:
                        continue

                score = d.pop("score", None) or d.pop("relevance_score", None) or 0.0
                out.append({
                    "id":       d.get("item_id") or d.get("id", ""),
                    "content":  d.get("content") or d.get("object_value") or "",
                    "score":    score,
                    "category": d.get("category", ""),
                    "entity":   d.get("subject") or d.get("entity_name", ""),
                    **{k: v for k, v in d.items() if k not in
                       ("item_id", "id", "content", "object_value", "score",
                        "relevance_score", "category", "entity_name", "subject")},
                })

                if len(out) >= requested_limit:
                    break

            items = out[:requested_limit]

        except Exception as exc:
            err = f"Memory search failed: {exc}"
            logger.error(err, exc_info=True)
            errors.append(err)

        return MemorySearchResponse(items=items, total=len(items), errors=errors)
