"""CRUD operations, personal facts, and reflections for Timmy's memory system."""

import json
import logging
import sqlite3
import uuid
from datetime import UTC, datetime, timedelta

from timmy.memory.db import MemoryEntry, get_connection
from timmy.memory.embeddings import (
    _get_embedding_model,
    _keyword_overlap,
    cosine_similarity,
    embed_text,
)

logger = logging.getLogger(__name__)


def store_memory(
    content: str,
    source: str,
    context_type: str = "conversation",
    agent_id: str | None = None,
    task_id: str | None = None,
    session_id: str | None = None,
    metadata: dict | None = None,
    compute_embedding: bool = True,
) -> MemoryEntry:
    """Store a memory entry with optional embedding."""
    embedding = None
    if compute_embedding:
        embedding = embed_text(content)

    entry = MemoryEntry(
        content=content,
        source=source,
        context_type=context_type,
        agent_id=agent_id,
        task_id=task_id,
        session_id=session_id,
        metadata=metadata,
        embedding=embedding,
    )

    with get_connection() as conn:
        conn.execute(
            """
            INSERT INTO memories
            (id, content, memory_type, source, agent_id, task_id, session_id,
             metadata, embedding, created_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                entry.id,
                entry.content,
                entry.context_type,  # DB column is memory_type
                entry.source,
                entry.agent_id,
                entry.task_id,
                entry.session_id,
                json.dumps(metadata) if metadata else None,
                json.dumps(embedding) if embedding else None,
                entry.timestamp,
            ),
        )
        conn.commit()

    return entry


def _build_search_filters(
    context_type: str | None,
    agent_id: str | None,
    session_id: str | None,
) -> tuple[str, list]:
    """Build SQL WHERE clause and params from search filters."""
    conditions: list[str] = []
    params: list = []

    if context_type:
        conditions.append("memory_type = ?")
        params.append(context_type)
    if agent_id:
        conditions.append("agent_id = ?")
        params.append(agent_id)
    if session_id:
        conditions.append("session_id = ?")
        params.append(session_id)

    where_clause = "WHERE " + " AND ".join(conditions) if conditions else ""
    return where_clause, params


def _fetch_memory_candidates(
    where_clause: str, params: list, candidate_limit: int
) -> list[sqlite3.Row]:
    """Fetch candidate memory rows from the database."""
    query_sql = f"""
        SELECT * FROM memories
        {where_clause}
        ORDER BY created_at DESC
        LIMIT ?
    """
    params.append(candidate_limit)

    with get_connection() as conn:
        return conn.execute(query_sql, params).fetchall()


def _row_to_entry(row: sqlite3.Row) -> MemoryEntry:
    """Convert a database row to a MemoryEntry."""
    return MemoryEntry(
        id=row["id"],
        content=row["content"],
        source=row["source"],
        context_type=row["memory_type"],  # DB column -> API field
        agent_id=row["agent_id"],
        task_id=row["task_id"],
        session_id=row["session_id"],
        metadata=json.loads(row["metadata"]) if row["metadata"] else None,
        embedding=json.loads(row["embedding"]) if row["embedding"] else None,
        timestamp=row["created_at"],
    )


def _score_and_filter(
    rows: list[sqlite3.Row],
    query: str,
    query_embedding: list[float],
    min_relevance: float,
) -> list[MemoryEntry]:
    """Score candidate rows by similarity and filter by min_relevance."""
    results = []
    for row in rows:
        entry = _row_to_entry(row)

        if entry.embedding:
            score = cosine_similarity(query_embedding, entry.embedding)
        else:
            score = _keyword_overlap(query, entry.content)

        entry.relevance_score = score
        if score >= min_relevance:
            results.append(entry)

    results.sort(key=lambda x: x.relevance_score or 0, reverse=True)
    return results


def search_memories(
    query: str,
    limit: int = 10,
    context_type: str | None = None,
    agent_id: str | None = None,
    session_id: str | None = None,
    min_relevance: float = 0.0,
) -> list[MemoryEntry]:
    """Search for memories by semantic similarity.

    Args:
        query: Search query text
        limit: Maximum results
        context_type: Filter by memory type (maps to DB memory_type column)
        agent_id: Filter by agent
        session_id: Filter by session
        min_relevance: Minimum similarity score (0-1)

    Returns:
        List of MemoryEntry objects sorted by relevance
    """
    query_embedding = embed_text(query)
    where_clause, params = _build_search_filters(context_type, agent_id, session_id)
    rows = _fetch_memory_candidates(where_clause, params, limit * 3)
    results = _score_and_filter(rows, query, query_embedding, min_relevance)
    return results[:limit]


def delete_memory(memory_id: str) -> bool:
    """Delete a memory entry by ID.

    Returns:
        True if deleted, False if not found
    """
    with get_connection() as conn:
        cursor = conn.execute(
            "DELETE FROM memories WHERE id = ?",
            (memory_id,),
        )
        conn.commit()
        return cursor.rowcount > 0


def get_memory_stats() -> dict:
    """Get statistics about the memory store.

    Returns:
        Dict with counts by type, total entries, etc.
    """
    with get_connection() as conn:
        total = conn.execute("SELECT COUNT(*) as count FROM memories").fetchone()["count"]

        by_type = {}
        rows = conn.execute(
            "SELECT memory_type, COUNT(*) as count FROM memories GROUP BY memory_type"
        ).fetchall()
        for row in rows:
            by_type[row["memory_type"]] = row["count"]

        with_embeddings = conn.execute(
            "SELECT COUNT(*) as count FROM memories WHERE embedding IS NOT NULL"
        ).fetchone()["count"]

    return {
        "total_entries": total,
        "by_type": by_type,
        "with_embeddings": with_embeddings,
        "has_embedding_model": _get_embedding_model() is not False,
    }


def prune_memories(older_than_days: int = 90, keep_facts: bool = True) -> int:
    """Delete old memories to manage storage.

    Args:
        older_than_days: Delete memories older than this
        keep_facts: Whether to preserve fact-type memories

    Returns:
        Number of entries deleted
    """
    cutoff = (datetime.now(UTC) - timedelta(days=older_than_days)).isoformat()

    with get_connection() as conn:
        if keep_facts:
            cursor = conn.execute(
                """
                DELETE FROM memories
                WHERE created_at < ? AND memory_type != 'fact'
                """,
                (cutoff,),
            )
        else:
            cursor = conn.execute(
                "DELETE FROM memories WHERE created_at < ?",
                (cutoff,),
            )

        deleted = cursor.rowcount
        conn.commit()

    return deleted


def get_memory_context(query: str, max_tokens: int = 2000, **filters) -> str:
    """Get relevant memory context as formatted text for LLM prompts.

    Args:
        query: Search query
        max_tokens: Approximate maximum tokens to return
        **filters: Additional filters (agent_id, session_id, etc.)

    Returns:
        Formatted context string for inclusion in prompts
    """
    memories = search_memories(query, limit=20, **filters)

    context_parts = []
    total_chars = 0
    max_chars = max_tokens * 4  # Rough approximation

    for mem in memories:
        formatted = f"[{mem.source}]: {mem.content}"
        if total_chars + len(formatted) > max_chars:
            break
        context_parts.append(formatted)
        total_chars += len(formatted)

    if not context_parts:
        return ""

    return "Relevant context from memory:\n" + "\n\n".join(context_parts)


# ── Personal facts & reflections ─────────────────────────────────────────────


def recall_personal_facts(agent_id: str | None = None) -> list[str]:
    """Recall personal facts about the user or system.

    Args:
        agent_id: Optional agent filter

    Returns:
        List of fact strings
    """
    with get_connection() as conn:
        if agent_id:
            rows = conn.execute(
                """
                SELECT content FROM memories
                WHERE memory_type = 'fact' AND agent_id = ?
                ORDER BY created_at DESC
                LIMIT 100
                """,
                (agent_id,),
            ).fetchall()
        else:
            rows = conn.execute(
                """
                SELECT content FROM memories
                WHERE memory_type = 'fact'
                ORDER BY created_at DESC
                LIMIT 100
                """,
            ).fetchall()

    return [r["content"] for r in rows]


def recall_personal_facts_with_ids(agent_id: str | None = None) -> list[dict]:
    """Recall personal facts with their IDs for edit/delete operations."""
    with get_connection() as conn:
        if agent_id:
            rows = conn.execute(
                "SELECT id, content FROM memories WHERE memory_type = 'fact' AND agent_id = ? ORDER BY created_at DESC LIMIT 100",
                (agent_id,),
            ).fetchall()
        else:
            rows = conn.execute(
                "SELECT id, content FROM memories WHERE memory_type = 'fact' ORDER BY created_at DESC LIMIT 100",
            ).fetchall()
    return [{"id": r["id"], "content": r["content"]} for r in rows]


def update_personal_fact(memory_id: str, new_content: str) -> bool:
    """Update a personal fact's content."""
    with get_connection() as conn:
        cursor = conn.execute(
            "UPDATE memories SET content = ? WHERE id = ? AND memory_type = 'fact'",
            (new_content, memory_id),
        )
        conn.commit()
        return cursor.rowcount > 0


def store_personal_fact(fact: str, agent_id: str | None = None) -> MemoryEntry:
    """Store a personal fact about the user or system.

    Args:
        fact: The fact to store
        agent_id: Associated agent

    Returns:
        The stored MemoryEntry
    """
    return store_memory(
        content=fact,
        source="system",
        context_type="fact",
        agent_id=agent_id,
        metadata={"auto_extracted": False},
    )


def store_last_reflection(reflection: str) -> None:
    """Store the last reflection, replacing any previous one.

    Uses a single row with memory_type='reflection' to avoid accumulation.
    """
    if not reflection or not reflection.strip():
        return
    with get_connection() as conn:
        # Delete previous reflections — only the latest matters
        conn.execute("DELETE FROM memories WHERE memory_type = 'reflection'")
        conn.execute(
            """
            INSERT INTO memories
            (id, content, memory_type, source, created_at)
            VALUES (?, ?, 'reflection', 'system', ?)
            """,
            (str(uuid.uuid4()), reflection.strip(), datetime.now(UTC).isoformat()),
        )
        conn.commit()
    logger.debug("Stored last reflection in DB")


def recall_last_reflection() -> str | None:
    """Recall the most recent reflection, or None if absent."""
    with get_connection() as conn:
        row = conn.execute(
            "SELECT content FROM memories WHERE memory_type = 'reflection' "
            "ORDER BY created_at DESC LIMIT 1"
        ).fetchone()
    return row["content"] if row else None
