"""
pgvector-backed long-term conversational memory.

Stores conversation turns as 384-dimensional embeddings (all-MiniLM-L6-v2)
and retrieves semantically relevant past exchanges using HNSW cosine similarity
search.

CRITICAL SECURITY CONSTRAINTS:
1. ALL queries MUST pre-filter by (tenant_id, agent_id, user_id) BEFORE the
   ANN operator. This prevents cross-tenant, cross-agent, or cross-user data
   leakage even in the face of embedding collisions.
2. Cosine similarity threshold filters out low-relevance results — only content
   genuinely related to the query should be injected into the LLM prompt.
3. RLS (Row Level Security) is the DB-level backstop — the application-level
   filters above are the primary guard; RLS is the safety net.

pgvector cosine operations:
- <=> operator: cosine DISTANCE (0 = identical, 2 = opposite)
- cosine similarity = 1 - cosine distance
- A threshold of 0.75 means: only return results where
  1 - (embedding <=> query) >= 0.75  →  distance <= 0.25
"""

from __future__ import annotations

import logging
import uuid

from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)


async def retrieve_relevant(
    session: AsyncSession,
    tenant_id: uuid.UUID,
    agent_id: uuid.UUID,
    user_id: str,
    query_embedding: list[float],
    top_k: int = 3,
    threshold: float = 0.75,
) -> list[str]:
    """
    Retrieve semantically relevant past conversation content.

    Performs an HNSW approximate nearest neighbor search scoped strictly to
    (tenant_id, agent_id, user_id). Results below the cosine similarity
    threshold are discarded.

    IMPORTANT: The (tenant_id, agent_id, user_id) pre-filter is applied
    BEFORE the ANN operator to guarantee isolation. This is not optional —
    removing these WHERE clauses would allow cross-tenant data leakage.

    Args:
        session:         Async SQLAlchemy session (must have RLS configured).
        tenant_id:       Tenant UUID — mandatory for isolation.
        agent_id:        Agent UUID — mandatory for isolation.
        user_id:         End-user identifier — mandatory for isolation.
        query_embedding: 384-dimensional query vector (all-MiniLM-L6-v2).
        top_k:           Maximum number of results to return. Default 3.
        threshold:       Minimum cosine similarity (0.0–1.0). Default 0.75.
                         Set lower for broader recall, higher for precision.

    Returns:
        List of content strings (original message text), most relevant first.
        Returns empty list if no results meet the threshold.
    """
    # Convert embedding list to pgvector string format: '[0.1, 0.2, ...]'
    vec_str = "[" + ",".join(str(float(v)) for v in query_embedding) + "]"

    # CRITICAL: pre-filter by all three isolation columns BEFORE ANN search.
    # The ORDER BY uses <=> (cosine distance) — lower is more similar.
    # We convert to similarity (1 - distance) to apply the threshold filter.
    stmt = text("""
        SELECT content, 1 - (embedding <=> CAST(:query AS vector)) AS similarity
        FROM conversation_embeddings
        WHERE tenant_id = :tenant_id
          AND agent_id = :agent_id
          AND user_id = :user_id
          AND 1 - (embedding <=> CAST(:query AS vector)) >= :threshold
        ORDER BY embedding <=> CAST(:query AS vector)
        LIMIT :top_k
    """)

    try:
        result = await session.execute(
            stmt,
            {
                "query": vec_str,
                "tenant_id": str(tenant_id),
                "agent_id": str(agent_id),
                "user_id": user_id,
                "threshold": threshold,
                "top_k": top_k,
            },
        )
        rows = result.fetchall()
    except Exception:
        logger.exception(
            "pgvector retrieve_relevant failed for tenant=%s agent=%s user=%s",
            tenant_id,
            agent_id,
            user_id,
        )
        return []

    return [row.content for row in rows]


async def store_embedding(
    session: AsyncSession,
    tenant_id: uuid.UUID,
    agent_id: uuid.UUID,
    user_id: str,
    content: str,
    role: str,
    embedding: list[float],
) -> None:
    """
    Store a conversation turn embedding in the database.

    Inserts a new row into conversation_embeddings. Embeddings are immutable
    once stored — there is no UPDATE path. This matches the audit-log-like
    nature of conversation history.

    Args:
        session:   Async SQLAlchemy session (must have RLS configured).
        tenant_id: Tenant UUID for isolation.
        agent_id:  Agent UUID for isolation.
        user_id:   End-user identifier for isolation.
        content:   Original message text.
        role:      "user" or "assistant".
        embedding: 384-dimensional float list (all-MiniLM-L6-v2).
    """
    vec_str = "[" + ",".join(str(float(v)) for v in embedding) + "]"

    stmt = text("""
        INSERT INTO conversation_embeddings
            (id, tenant_id, agent_id, user_id, content, role, embedding)
        VALUES
            (gen_random_uuid(), :tenant_id, :agent_id, :user_id, :content, :role, CAST(:embedding AS vector))
    """)

    try:
        await session.execute(
            stmt,
            {
                "tenant_id": str(tenant_id),
                "agent_id": str(agent_id),
                "user_id": user_id,
                "content": content,
                "role": role,
                "embedding": vec_str,
            },
        )
    except Exception:
        logger.exception(
            "pgvector store_embedding failed for tenant=%s agent=%s user=%s",
            tenant_id,
            agent_id,
            user_id,
        )
        raise
