"""
Discord Bridge Plugin for Cheshire Cat

This plugin enriches Cat's memory system with Discord context:
- Unified user identity across all servers and DMs
- Guild/channel metadata for context tracking
- Minimal filtering before storage (only skip obvious junk)
- Marks memories as unconsolidated for nightly processing

Phase 1 Implementation
"""

from cat.mad_hatter.decorators import hook
from datetime import datetime
import re


@hook(priority=100)
def before_cat_reads_message(user_message_json: dict, cat) -> dict:
    """
    Enrich incoming message with Discord metadata.
    This runs BEFORE the message is processed.
    
    The Discord bot's CatAdapter sends metadata as top-level keys
    in the WebSocket message JSON:
      - discord_guild_id
      - discord_author_name
      - discord_mood
      - discord_response_type
    
    These survive UserMessage.model_validate() as extra attributes
    (BaseModelDict has extra="allow"). We read them via .get() and
    store them in working_memory for downstream hooks.
    """
    # Extract Discord context from the message payload
    # (sent by CatAdapter.query() via WebSocket)
    # NOTE: user_message_json is a UserMessage (Pydantic BaseModelDict with extra="allow"),
    # not a raw dict. Extra keys survive model_validate() as extra attributes.
    # We use .get() since BaseModelDict implements it, but NOT .pop().
    guild_id = user_message_json.get('discord_guild_id', None)
    author_name = user_message_json.get('discord_author_name', None)
    mood = user_message_json.get('discord_mood', None)
    response_type = user_message_json.get('discord_response_type', None)

    # Also check working memory for backward compatibility
    if not guild_id:
        guild_id = cat.working_memory.get('guild_id')
    
    # Store in working memory so other hooks can access it
    cat.working_memory['guild_id'] = guild_id or 'dm'
    cat.working_memory['author_name'] = author_name
    cat.working_memory['mood'] = mood
    cat.working_memory['response_type'] = response_type
    
    return user_message_json


@hook(priority=100)
def before_cat_stores_episodic_memory(doc, cat):
    """
    Filter and enrich memories before storage.
    
    Phase 1: Minimal filtering
    - Skip only obvious junk (1-2 char messages, pure reactions)
    - Store everything else temporarily
    - Mark as unconsolidated for nightly processing
    """
    message = doc.page_content.strip()
    
    # Skip only the most trivial messages
    skip_patterns = [
        r'^\w{1,2}$',  # 1-2 character messages: "k", "ok"
        r'^(lol|lmao|haha|hehe|xd|rofl)$',  # Pure reactions
        r'^:[\w_]+:$',  # Discord emoji only: ":smile:"
    ]
    
    for pattern in skip_patterns:
        if re.match(pattern, message.lower()):
            print(f"🗑️  [Discord Bridge] Skipping trivial message: {message}")
            return None  # Don't store at all
    
    # Add Discord metadata to memory
    doc.metadata['consolidated'] = False  # Needs nightly processing
    doc.metadata['stored_at'] = datetime.now().isoformat()
    
    # Get Discord context from working memory (set by before_cat_reads_message)
    guild_id = cat.working_memory.get('guild_id', 'dm')
    author_name = cat.working_memory.get('author_name')
    
    doc.metadata['guild_id'] = guild_id
    doc.metadata['source'] = cat.user_id  # CRITICAL: Cat filters episodic by source=user_id!
    doc.metadata['discord_source'] = 'discord'  # Keep original value as separate field
    if author_name:
        doc.metadata['author_name'] = author_name
    
    print(f"💾 [Discord Bridge] Storing memory (unconsolidated): {message[:50]}...")
    print(f"   User: {cat.user_id}, Guild: {guild_id}, Author: {author_name}")
    
    return doc


@hook(priority=80)
def before_cat_recalls_declarative_memories(declarative_recall_config, cat):
    """
    Increase k-value and lower threshold for better declarative memory retrieval.
    
    Default Cat settings (k=3, threshold=0.7) are too restrictive for factual recall.
    We increase k to retrieve more candidates and lower threshold to catch facts
    that might have lower similarity scores due to embedding model limitations.
    """
    # Increase from k=3 to k=10 (retrieve more memories)
    declarative_recall_config["k"] = 10
    
    # Lower threshold from 0.7 to 0.5 (be more lenient with similarity scores)
    declarative_recall_config["threshold"] = 0.5
    
    print(f"🔧 [Discord Bridge] Adjusted declarative recall: k={declarative_recall_config['k']}, threshold={declarative_recall_config['threshold']}")
    
    return declarative_recall_config


@hook(priority=50)
def after_cat_recalls_memories(cat):
    """
    Log memory recall for debugging.
    Access recalled memories via cat.working_memory.
    """    
    # Get recalled memories from working memory
    episodic_memories = cat.working_memory.get('episodic_memories', [])
    declarative_memories = cat.working_memory.get('declarative_memories', [])
    
    if episodic_memories:
        print(f"🧠 [Discord Bridge] Recalled {len(episodic_memories)} episodic memories for user {cat.user_id}")
        guilds = set()
        for doc, score, *rest in episodic_memories:
            guild = doc.metadata.get('guild_id', 'unknown')
            guilds.add(guild)
        print(f"   From guilds: {', '.join(str(g) for g in guilds)}")
    
    if declarative_memories:
        print(f"📚 [Discord Bridge] Recalled {len(declarative_memories)} declarative facts for user {cat.user_id}")
        # Show the actual facts for debugging
        for doc, score, *rest in declarative_memories[:3]:  # Show top 3
            print(f"   - [{score:.3f}] {doc.page_content[:80]}...")


@hook(priority=100)
def agent_prompt_prefix(prefix, cat) -> str:
    """
    Add explicit instruction to respect declarative facts.
    This overrides the default Cat prefix to emphasize factual accuracy.
    """
    # Add a strong instruction about facts BEFORE the regular personality
    enhanced_prefix = f"""You are Hatsune Miku, a cheerful virtual idol.

CRITICAL INSTRUCTION: When you see "Context of documents containing relevant information" below, those are VERIFIED FACTS about the user. You MUST use these facts when they are relevant to the user's question. Never guess or make up information that contradicts these facts.

{prefix}"""
    
    return enhanced_prefix


@hook(priority=100)
def before_agent_starts(agent_input, cat) -> dict:
    """
    Log the agent input for debugging.
    Now that the suffix template is fixed, declarative facts should appear naturally.
    """
    declarative_mem = agent_input.get('declarative_memory', '')
    episodic_mem = agent_input.get('episodic_memory', '')
    
    print(f"🔍 [Discord Bridge] before_agent_starts called")
    print(f"   input: {agent_input.get('input', '')[:80]}")
    print(f"   declarative_mem length: {len(declarative_mem)}")
    print(f"   episodic_mem length: {len(episodic_mem)}")
    if declarative_mem:
        print(f"   declarative_mem preview: {declarative_mem[:200]}")
    
    return agent_input


@hook(priority=100)
def before_cat_sends_message(message: dict, cat) -> dict:
    """
    This hook is called AFTER the LLM response, so it's too late to modify the prompt.
    Keeping it for potential post-processing, but the real work happens in before_agent_starts.
    """
    return message


@hook(priority=10)
def agent_prompt_suffix(prompt_suffix, cat) -> str:
    """
    Pass through the suffix unchanged.
    The miku_personality plugin (priority=100) sets the suffix with memory placeholders.
    This lower-priority hook runs first but the miku_personality hook overrides it.
    """
    return prompt_suffix


# Plugin metadata
__version__ = "1.0.0"
__description__ = "Discord bridge with unified user identity and sleep consolidation support"
