"""cortex/api/routes/memory/browse.py — Read-only browse, detail, categories, timeline, entities."""

from __future__ import annotations

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

from fastapi import Depends, HTTPException, Query, Request, status

from cortex.api.deps import PREFIX, _resolve_owner_id, _serialise, get_plugin
from cortex.api.models import (
    CategoryListResponse,
    MemoryBrowseResponse,
    MemoryDetailResponse,
    MemoryTimelineResponse,
    ReviewQueueResponse,
    TopEntitiesResponse,
)

logger = logging.getLogger(__name__)


def _register(router, verify_api_key):
    """Register browse/read-only memory endpoints."""

    # NOTE: /memories/categories MUST come before /memories/{memory_id}
    # so FastAPI doesn't interpret "categories" as a path param.
    @router.get(
        f"{PREFIX}/memories/categories",
        response_model=CategoryListResponse,
        summary="List memory categories with counts",
        tags=["memories"],
        operation_id="list_memory_categories",
    )
    async def list_memory_categories(
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> CategoryListResponse:
        """Return all memory categories with per-category claim counts."""
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            errors.append("Storage not available")
            return CategoryListResponse(errors=errors)

        owner_id = getattr(plugin.config, "owner_id", "default")

        _CATEGORY_META: Dict[str, Dict[str, str]] = {
            "identity":    {"color": "#6366f1", "description": "Who the user is"},
            "personal":    {"color": "#ec4899", "description": "Personal facts and info"},
            "preferences": {"color": "#f59e0b", "description": "Preferences and likes/dislikes"},
            "decisions":   {"color": "#10b981", "description": "Architecture choices and business decisions"},
            "goals":       {"color": "#3b82f6", "description": "Goals and aspirations"},
            "behavioral":  {"color": "#8b5cf6", "description": "Habits, patterns, and routines"},
            "relational":  {"color": "#14b8a6", "description": "Relationships and social dynamics"},
            "episodic":    {"color": "#f97316", "description": "Events, experiences, and milestones"},
            "misc":        {"color": "#6b7280", "description": "Miscellaneous memories"},
        }

        try:
            db_counts = await storage.get_category_counts(owner_id=owner_id)
        except Exception as exc:
            errors.append(f"Category fetch failed: {exc}")
            db_counts = []

        counts_by_name = {r["name"]: r["count"] for r in db_counts}
        all_names = set(_CATEGORY_META.keys()) | set(counts_by_name.keys())
        categories = []
        for name in sorted(all_names):
            meta = _CATEGORY_META.get(name, {"color": "#6b7280", "description": name})
            categories.append({
                "name": name,
                "count": counts_by_name.get(name, 0),
                "color": meta["color"],
                "description": meta["description"],
            })

        return CategoryListResponse(categories=categories, errors=errors)

    @router.get(
        f"{PREFIX}/memories",
        response_model=MemoryBrowseResponse,
        summary="Browse and filter memories",
        tags=["memories"],
        operation_id="browse_memories",
    )
    async def browse_memories(
        request: Request,
        page: int = 1,
        per_page: int = 50,
        time_range: str = "all",
        status: Optional[str] = None,
        category: Optional[str] = None,
        entity_name: Optional[str] = None,
        entity_id: Optional[str] = Query(None, description="Filter by subject_entity_id (#923)"),
        q: Optional[str] = None,
        sort_by: Literal["created_at", "updated_at", "subject", "access_count", "mentions"] = "updated_at",
        sort_order: Literal["asc", "desc"] = "desc",
        owner_id: Optional[str] = None,
        include_deleted: bool = Query(False, description="Include soft-deleted memories (admin)"),
        include_expired: bool = Query(False, description="Include temporally expired claims (#925)"),
        exclude_source_session: Optional[str] = Query(None, description="Exclude claims from this source session (#1220)"),
        include_embeddings: bool = Query(False, description="Include raw embedding vectors as base64 (#R-297)"),
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> MemoryBrowseResponse:
        """Browse semantic memories with filtering, pagination, and keyword search.

        Pass ``include_deleted=true`` to see soft-deleted memories (admin only).
        """
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            errors.append("Storage not available")
            return MemoryBrowseResponse(errors=errors)

        effective_owner = _resolve_owner_id(request, owner_id)
        per_page = max(1, min(per_page, 200))
        page = max(1, page)

        try:
            result = await storage.browse_memories(
                owner_id=effective_owner,
                page=page,
                per_page=per_page,
                time_range=time_range,
                status=status,
                category=category,
                entity_name=entity_name,
                q=q,
                sort_by=sort_by,
                sort_order=sort_order,
                include_deleted=include_deleted,
                include_expired=include_expired,
                exclude_source_session=exclude_source_session,
                include_embeddings=include_embeddings,
            )
        except Exception as exc:
            err = f"Memory browse failed: {exc}"
            logger.error(err, exc_info=True)
            errors.append(err)
            return MemoryBrowseResponse(errors=errors)

        return MemoryBrowseResponse(
            items=result["items"],
            total=result["total"],
            page=result["page"],
            per_page=result["per_page"],
            pages=result["pages"],
            category_counts=result["category_counts"],
            errors=errors,
        )

    # NOTE: /memories/review-queue MUST come before /memories/{memory_id}
    # so FastAPI doesn't interpret "review-queue" as a path param.
    @router.get(
        f"{PREFIX}/memories/review-queue",
        response_model=ReviewQueueResponse,
        summary="List flagged memories",
        tags=["review-queue"],
        operation_id="review_queue",
    )
    async def review_queue(
        request: Request,
        page: int = 1,
        per_page: int = 50,
        category: Optional[str] = None,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> ReviewQueueResponse:
        """List all memories flagged for review (status='needs_review')."""
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            errors.append("Storage not available")
            return ReviewQueueResponse(errors=errors)

        effective_owner = _resolve_owner_id(request, owner_id)
        per_page = max(1, min(per_page, 200))
        page = max(1, page)

        try:
            result = await storage.browse_memories(
                owner_id=effective_owner,
                page=page,
                per_page=per_page,
                status="needs_review",
                category=category,
                sort_by="updated_at",
                sort_order="desc",
            )
        except Exception as exc:
            err = f"Review queue query failed: {exc}"
            logger.error(err, exc_info=True)
            errors.append(err)
            return ReviewQueueResponse(errors=errors)

        return ReviewQueueResponse(
            items=result["items"],
            total=result["total"],
            page=result["page"],
            per_page=result["per_page"],
            pages=result["pages"],
            errors=errors,
        )

    # NOTE: /memories/timeline MUST come before /memories/{memory_id}
    @router.get(
        f"{PREFIX}/memories/timeline",
        response_model=MemoryTimelineResponse,
        summary="Temporal history of claims for a subject",
        tags=["memories"],
        operation_id="memory_timeline",
    )
    async def memory_timeline(
        request: Request,
        subject: str = Query(..., description="Subject to retrieve timeline for"),
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> MemoryTimelineResponse:
        """Return all claims (including expired) for a subject, ordered by valid_from (#925)."""
        storage = getattr(plugin, "storage", None)
        if storage is None:
            return MemoryTimelineResponse(subject=subject, errors=["Storage not available"])
        effective_owner = _resolve_owner_id(request, owner_id)
        try:
            claims = await storage.get_claims_timeline(subject, owner_id=effective_owner)
            items = [
                {
                    "id": c.id, "subject": c.subject, "predicate": c.predicate,
                    "object_value": c.object_value, "status": c.status,
                    "valid_from": c.valid_from, "valid_to": c.valid_to,
                    "created_at": c.created_at, "updated_at": c.updated_at,
                    "superseded_by": c.superseded_by, "confidence": c.confidence,
                    "subject_entity_id": c.subject_entity_id,
                }
                for c in claims
            ]
        except Exception as exc:
            return MemoryTimelineResponse(subject=subject, errors=[f"Timeline query failed: {exc}"])
        return MemoryTimelineResponse(subject=subject, items=items, total=len(items))

    @router.get(
        f"{PREFIX}/memories/{{memory_id}}",
        response_model=MemoryDetailResponse,
        summary="Get full memory detail",
        tags=["memories"],
        operation_id="get_memory",
    )
    async def get_memory_detail(
        request: Request,
        memory_id: str,
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> MemoryDetailResponse:
        """Return full claim data, changelog, and related memories (same subject entity)."""
        errors: List[str] = []
        storage = getattr(plugin, "storage", None)
        if storage is None:
            errors.append("Storage not available")
            return MemoryDetailResponse(errors=errors)

        effective_owner = _resolve_owner_id(request, owner_id)
        claim = await storage.get_claim(memory_id, owner_id=effective_owner)
        if claim is None:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Memory '{memory_id}' not found",
            )

        memory_dict = _serialise(claim.__dict__) if hasattr(claim, "__dict__") else {"id": memory_id}

        try:
            changelog = await storage.get_changelog(memory_id)
        except Exception as exc:
            errors.append(f"Changelog fetch failed: {exc}")
            changelog = []

        provenance: List[Dict[str, Any]] = []
        try:
            prov_records = await storage.get_provenance(memory_id)
            for p in prov_records:
                provenance.append(_serialise(p.__dict__) if hasattr(p, "__dict__") else dict(p))
        except Exception as exc:
            errors.append(f"Provenance fetch failed: {exc}")

        related: List[Dict[str, Any]] = []
        try:
            entity_id = getattr(claim, "subject_entity_id", None)
            if entity_id:
                siblings = await storage.get_claims_by_entity(
                    entity_id,
                    owner_id=effective_owner,
                )
                for s in siblings:
                    if s.id != memory_id:
                        related.append({
                            "id": s.id,
                            "subject": s.subject,
                            "predicate": s.predicate,
                            "object_value": (s.object_value[:200] if s.object_value else ""),
                            "category": getattr(s, "category", "misc"),
                            "confidence": s.confidence,
                            "status": s.status,
                        })
                        if len(related) >= 5:
                            break
            else:
                siblings = await storage.get_claims_by_subject(
                    claim.subject,
                    owner_id=effective_owner,
                )
                for s in siblings:
                    if s.id != memory_id:
                        related.append({
                            "id": s.id,
                            "subject": s.subject,
                            "predicate": s.predicate,
                            "object_value": (s.object_value[:200] if s.object_value else ""),
                            "category": getattr(s, "category", "misc"),
                            "confidence": s.confidence,
                            "status": s.status,
                        })
                        if len(related) >= 5:
                            break
        except Exception as exc:
            errors.append(f"Related memories fetch failed: {exc}")

        return MemoryDetailResponse(
            memory=memory_dict,
            changelog=changelog,
            related=related,
            provenance=provenance,
            errors=errors,
        )

    # ---- Entities: top by mention count (#924) ----
    @router.get(
        f"{PREFIX}/entities/top",
        response_model=TopEntitiesResponse,
        summary="List top entities by mention count",
        tags=["entities"],
        operation_id="get_top_entities",
    )
    async def get_top_entities(
        request: Request,
        min_mentions: int = Query(2, ge=1, description="Minimum mention_count threshold"),
        limit: int = Query(50, ge=1, le=200, description="Max results"),
        owner_id: Optional[str] = None,
        _: None = Depends(verify_api_key),
        plugin: Any = Depends(get_plugin),
    ) -> TopEntitiesResponse:
        """Return entities sorted by mention_count DESC (#924)."""
        storage = getattr(plugin, "storage", None)
        if storage is None:
            return TopEntitiesResponse(errors=["Storage not available"])
        effective_owner = _resolve_owner_id(request, owner_id)
        try:
            rows = await storage.get_entities_by_mentions(
                owner_id=effective_owner, min_mentions=min_mentions, limit=limit,
            )
        except Exception as exc:
            return TopEntitiesResponse(errors=[f"Entity query failed: {exc}"])
        return TopEntitiesResponse(items=rows, total=len(rows))
