#!/usr/bin/env python3
"""
DEPRECATED — Memory querying is now part of the canonical unified server at:
    mcp-servers/sunaiva-memory/server.py (memory_search tool)

Migrate to the canonical server which provides:
  - 768-dim semantic search (standardised)
  - Single Qdrant collection (sunaiva_memory_768)
  - PostgreSQL keyword fallback
  - Redis caching
  - User isolation

This file is retained for backward compatibility only. Do NOT build on it.
Deprecation date: 2026-02-26.

--- Original docstring below ---

Sunaiva Memory MCP — Memory Query Engine
=========================================
Accepts a natural language query, searches the Qdrant semantic index,
and returns relevant memory chunks with source context.

Also includes PostgreSQL fallback for keyword search when Qdrant
vectors are zero (gemini-embedding-001 unavailable at ingest time).

Usage:
    python memory_query.py --query "What tech stack did I decide on?" --user_id kinan
    python memory_query.py --query "decisions about pricing" --user_id kinan --top_k 5

    # Or import and call:
    from memory_query import query_memory
    results = query_memory("What was my tech stack decision?", user_id="kinan")
    for r in results:
        print(r["text"])
        print(r["source"])
"""

import json
import logging
import sys
from pathlib import Path
from typing import Optional

# ── Path bootstrap ────────────────────────────────────────────────────────────
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "data" / "genesis-memory"))

from elestio_config import PostgresConfig, QdrantConfig

try:
    import psycopg2
except ImportError:
    raise ImportError("Install psycopg2-binary: pip install psycopg2-binary")

try:
    from qdrant_client import QdrantClient
    from qdrant_client.models import Filter, FieldCondition, MatchValue
except ImportError:
    raise ImportError("Install qdrant-client: pip install qdrant-client")

# Reuse embedding function from ingestor
from google_takeout_ingestor import get_embedding

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
    datefmt="%H:%M:%S",
)
log = logging.getLogger("sunaiva-query")


# ════════════════════════════════════════════════════════════════════════════
# Semantic search via Qdrant
# ════════════════════════════════════════════════════════════════════════════

def search_qdrant(
    query: str,
    user_id: str,
    top_k: int = 5,
    score_threshold: float = 0.30,
) -> list[dict]:
    """
    Vector-search the Qdrant collection for the given user.

    Returns a list of result dicts:
        {
            "score":                float,
            "text":                 str,
            "conversation_id":      str,
            "conversation_title":   str,
            "created_at":           str,
            "chunk_index":          int,
            "source":               "qdrant",
        }
    """
    collection_name = f"sunaiva_memory_{user_id}"

    try:
        client = QdrantClient(**QdrantConfig.get_client_params())

        # Confirm collection exists
        existing = {c.name for c in client.get_collections().collections}
        if collection_name not in existing:
            log.warning(f"Collection {collection_name} not found. Run ingestion first.")
            return []

        # Embed the query
        query_vector = get_embedding(query)
        if all(v == 0.0 for v in query_vector):
            log.warning("Query vector is zero — gemini-embedding-001 unavailable. Qdrant search skipped.")
            return []

        # Filter to this user only (defensive; collection is already user-scoped)
        user_filter = Filter(
            must=[FieldCondition(key="user_id", match=MatchValue(value=user_id))]
        )

        hits = client.search(
            collection_name=collection_name,
            query_vector=query_vector,
            query_filter=user_filter,
            limit=top_k,
            score_threshold=score_threshold,
        )

        results = []
        for hit in hits:
            payload = hit.payload or {}
            results.append(
                {
                    "score": round(hit.score, 4),
                    "text": payload.get("text", ""),
                    "conversation_id": payload.get("conversation_id", ""),
                    "conversation_title": payload.get("conversation_title", ""),
                    "created_at": payload.get("created_at", ""),
                    "chunk_index": payload.get("chunk_index", 0),
                    "source": "qdrant",
                }
            )
        log.info(f"Qdrant returned {len(results)} results for query: '{query[:60]}'")
        return results

    except Exception as e:
        log.error(f"Qdrant search failed: {e}")
        return []


# ════════════════════════════════════════════════════════════════════════════
# PostgreSQL keyword fallback
# ════════════════════════════════════════════════════════════════════════════

def search_postgres_fulltext(
    query: str,
    user_id: str,
    top_k: int = 5,
) -> list[dict]:
    """
    Full-text keyword fallback when Qdrant vectors are unavailable.
    Uses PostgreSQL ts_vector for ranked search across sunaiva_chunks.

    Returns same shape as search_qdrant results.
    """
    try:
        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()

        # Use plainto_tsquery for safe natural-language query parsing
        cur.execute(
            """
            SELECT
                sc.chunk_id,
                sc.conversation_id,
                sc.text,
                sc.created_at,
                sv.title,
                ts_rank(to_tsvector('english', sc.text), plainto_tsquery('english', %s)) AS rank
            FROM sunaiva_chunks sc
            LEFT JOIN sunaiva_conversations sv
                ON sc.conversation_id = sv.id
            WHERE sc.user_id = %s
              AND to_tsvector('english', sc.text) @@ plainto_tsquery('english', %s)
            ORDER BY rank DESC
            LIMIT %s;
            """,
            (query, user_id, query, top_k),
        )
        rows = cur.fetchall()
        cur.close()
        conn.close()

        results = []
        for row in rows:
            chunk_id, conv_id, text, created_at, title, rank = row
            results.append(
                {
                    "score": round(float(rank), 4),
                    "text": text or "",
                    "conversation_id": conv_id or "",
                    "conversation_title": title or "",
                    "created_at": str(created_at) if created_at else "",
                    "chunk_index": 0,
                    "source": "postgres_fts",
                }
            )
        log.info(f"PostgreSQL FTS returned {len(results)} results for query: '{query[:60]}'")
        return results

    except Exception as e:
        log.error(f"PostgreSQL FTS search failed: {e}")
        return []


def search_postgres_recent(
    user_id: str,
    limit: int = 5,
) -> list[dict]:
    """Return the most recently ingested memory chunks — useful as context priming."""
    try:
        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            """
            SELECT sc.chunk_id, sc.conversation_id, sc.text, sc.created_at, sv.title
            FROM sunaiva_chunks sc
            LEFT JOIN sunaiva_conversations sv ON sc.conversation_id = sv.id
            WHERE sc.user_id = %s
            ORDER BY sc.ingested_at DESC
            LIMIT %s;
            """,
            (user_id, limit),
        )
        rows = cur.fetchall()
        cur.close()
        conn.close()
        return [
            {
                "score": 0.0,
                "text": row[2] or "",
                "conversation_id": row[1] or "",
                "conversation_title": row[4] or "",
                "created_at": str(row[3]) if row[3] else "",
                "chunk_index": 0,
                "source": "postgres_recent",
            }
            for row in rows
        ]
    except Exception as e:
        log.error(f"Recent chunks query failed: {e}")
        return []


# ════════════════════════════════════════════════════════════════════════════
# Entity & decision lookups
# ════════════════════════════════════════════════════════════════════════════

def get_user_entities(user_id: str) -> list[str]:
    """Return all technology entities extracted for a user."""
    try:
        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            "SELECT name FROM sunaiva_entities WHERE user_id = %s ORDER BY name",
            (user_id,),
        )
        names = [row[0] for row in cur.fetchall()]
        cur.close()
        conn.close()
        return names
    except Exception as e:
        log.error(f"Entity lookup failed: {e}")
        return []


def get_user_decisions(user_id: str, limit: int = 20) -> list[dict]:
    """Return captured decision records for a user."""
    try:
        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            """
            SELECT id, conversation_id, text, event_timestamp
            FROM sunaiva_decisions
            WHERE user_id = %s
            ORDER BY ingested_at DESC
            LIMIT %s;
            """,
            (user_id, limit),
        )
        rows = cur.fetchall()
        cur.close()
        conn.close()
        return [
            {
                "id": row[0],
                "conversation_id": row[1],
                "text": row[2],
                "timestamp": str(row[3]) if row[3] else "",
            }
            for row in rows
        ]
    except Exception as e:
        log.error(f"Decision lookup failed: {e}")
        return []


def get_ingestion_stats(user_id: str) -> Optional[dict]:
    """Return the latest ingestion stats for a user from the log table."""
    try:
        conn = psycopg2.connect(**PostgresConfig.get_connection_params())
        cur = conn.cursor()
        cur.execute(
            """
            SELECT conversations, messages, chunks, entities, decisions,
                   topics, status, completed_at, source_file
            FROM sunaiva_ingestion_log
            WHERE user_id = %s
            ORDER BY id DESC
            LIMIT 1;
            """,
            (user_id,),
        )
        row = cur.fetchone()
        cur.close()
        conn.close()
        if not row:
            return None
        return {
            "conversations": row[0],
            "messages": row[1],
            "chunks": row[2],
            "entities": row[3],
            "decisions": row[4],
            "topics": row[5],
            "status": row[6],
            "completed_at": str(row[7]) if row[7] else "",
            "source_file": row[8],
        }
    except Exception as e:
        log.error(f"Ingestion stats lookup failed: {e}")
        return None


# ════════════════════════════════════════════════════════════════════════════
# Primary query interface
# ════════════════════════════════════════════════════════════════════════════

def query_memory(
    query: str,
    user_id: str = "kinan",
    top_k: int = 5,
    include_decisions: bool = False,
    include_entities: bool = False,
) -> dict:
    """
    Primary query interface. Tries Qdrant semantic search first;
    falls back to PostgreSQL full-text search if Qdrant is unavailable
    or returns no results.

    Returns:
        {
            "query":          str,
            "user_id":        str,
            "results":        list[dict],   # ranked memory chunks
            "source":         str,          # "qdrant" | "postgres_fts" | "none"
            "entities":       list[str],    # optional
            "decisions":      list[dict],   # optional
            "formatted":      str,          # human-readable output
        }
    """
    log.info(f"Querying memory for user={user_id}: '{query}'")

    results = search_qdrant(query, user_id, top_k=top_k)
    source = "qdrant"

    if not results:
        log.info("Qdrant returned nothing — falling back to PostgreSQL FTS.")
        results = search_postgres_fulltext(query, user_id, top_k=top_k)
        source = "postgres_fts" if results else "none"

    if not results:
        log.info("No results from either backend — returning recent chunks as context.")
        results = search_postgres_recent(user_id, limit=top_k)
        source = "postgres_recent" if results else "none"

    # Optional enrichment
    entities = get_user_entities(user_id) if include_entities else []
    decisions = get_user_decisions(user_id) if include_decisions else []

    # Format human-readable output
    formatted = _format_results(query, results, entities, decisions)

    return {
        "query": query,
        "user_id": user_id,
        "results": results,
        "source": source,
        "entities": entities,
        "decisions": decisions,
        "formatted": formatted,
    }


def _format_results(
    query: str,
    results: list[dict],
    entities: list[str],
    decisions: list[dict],
) -> str:
    """Format results as a readable memory recall report."""
    lines = [
        f"Memory Recall: \"{query}\"",
        f"Results: {len(results)} match(es)",
        "",
    ]

    if not results:
        lines.append("No matching memories found.")
    else:
        for i, r in enumerate(results, 1):
            score_str = f"  [score: {r['score']:.3f}]" if r["score"] > 0 else ""
            title = r.get("conversation_title") or r.get("conversation_id") or "Unknown"
            date_str = f"  ({r['created_at'][:10]})" if r.get("created_at") else ""
            lines.append(f"--- {i}. {title}{date_str}{score_str} ---")
            # Show up to 400 chars of the chunk text
            text = r.get("text", "")
            lines.append(text[:400] + ("..." if len(text) > 400 else ""))
            lines.append("")

    if entities:
        lines.append(f"Your tech stack: {', '.join(entities[:20])}")
        lines.append("")

    if decisions:
        lines.append(f"Recent decisions ({len(decisions)}):")
        for d in decisions[:5]:
            ts = d.get("timestamp", "")[:10] if d.get("timestamp") else "?"
            lines.append(f"  [{ts}] {d.get('text', '')[:120]}")

    return "\n".join(lines)


# ════════════════════════════════════════════════════════════════════════════
# CLI entry point
# ════════════════════════════════════════════════════════════════════════════

if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(
        description="Query Sunaiva Memory MCP — semantic search over your AI conversation history"
    )
    parser.add_argument("--query", required=True, help="Natural language query")
    parser.add_argument("--user_id", default="kinan", help="User ID (default: kinan)")
    parser.add_argument("--top_k", type=int, default=5, help="Number of results (default: 5)")
    parser.add_argument("--decisions", action="store_true", help="Include decision history")
    parser.add_argument("--entities", action="store_true", help="Include entity/tech list")
    parser.add_argument("--stats", action="store_true", help="Show ingestion stats and exit")
    args = parser.parse_args()

    if args.stats:
        stats = get_ingestion_stats(args.user_id)
        if stats:
            print(json.dumps(stats, indent=2, default=str))
        else:
            print(f"No ingestion stats found for user: {args.user_id}")
        sys.exit(0)

    output = query_memory(
        query=args.query,
        user_id=args.user_id,
        top_k=args.top_k,
        include_decisions=args.decisions,
        include_entities=args.entities,
    )

    print("\n" + "=" * 60)
    print(output["formatted"])
    print(f"\n[Source backend: {output['source']}]")
    print("=" * 60)
