"""
User Memory Extractor — Rule-based extraction of user preferences,
constraints, and environment info from agent messages.

PRD section 7: Simple rule matching on patterns like:
    "prefer X", "use X", "avoid X", "always use X"
"""

import re
import logging
from typing import List, Tuple, Optional
from openmemo.user_memory.user_memory_store import UserMemoryNode

logger = logging.getLogger("openmemo.user_memory")

# ── Rule tables ───────────────────────────────────────────────────────────────

# (pattern, mem_type, confidence)
_RULES: List[Tuple[str, str, float]] = [
    # Preferences
    (r"(?:I\s+)?prefer(?:s|red)?\s+(.+?)(?:\s+over\s+.+)?$",       "preference", 0.85),
    (r"always\s+use\s+(.+)",                                          "preference", 0.90),
    (r"(?:I\s+)?like(?:s)?\s+(?:to\s+use\s+)?(.+)",                 "preference", 0.75),
    (r"(?:I\s+)?want\s+(?:to\s+use\s+)?(.+)",                       "preference", 0.70),
    (r"(?:please\s+)?use\s+(.+?)\s+(?:instead|for|when|if)",         "preference", 0.80),
    (r"my\s+(?:preferred\s+)?\w+\s+is\s+(.+)",                      "preference", 0.85),
    # Constraints
    (r"(?:avoid|never\s+use|don['\u2019]?t\s+use|do\s+not\s+use)\s+(.+)", "constraint", 0.90),
    (r"(?:no|without)\s+(.+?)(?:\s+please)?$",                       "constraint", 0.75),
    (r"(?:I\s+)?(?:don['\u2019]?t|do\s+not)\s+want\s+(.+)",         "constraint", 0.80),
    (r"(?:instead\s+of|not)\s+(.+)",                                  "constraint", 0.70),
    # Environment (key: value style)
    (r"^(\w[\w\s]*?):\s*(.+)$",                                       "environment", 0.80),
    # Behavior
    (r"(?:I\s+)?prefer(?:s)?\s+(?:shorter|concise|brief)\s+(.+)",   "behavior",   0.75),
    (r"(?:I\s+)?prefer(?:s)?\s+(?:detailed|thorough|verbose)\s+(.+)", "behavior", 0.75),
]

# Keywords that signal environment context
_ENV_KEYS = {
    "database", "db", "cloud", "env", "environment", "os", "platform",
    "language", "framework", "runtime", "deploy", "server", "region",
    "stack", "infra", "infrastructure",
}

# Stop words — too generic to store as memory
_NOISE_VALUES = {
    "it", "this", "that", "the", "a", "an", "here", "there",
    "yes", "no", "ok", "okay", "sure", "right",
}


# ── Extractor ─────────────────────────────────────────────────────────────────

class UserMemoryExtractor:
    """
    Extracts UserMemoryNodes from free-text agent messages using rule-based matching.

    Usage:
        extractor = UserMemoryExtractor()
        nodes = extractor.extract("user_1", "Use docker-compose instead of Kubernetes")
        # -> [UserMemoryNode(type='preference', key='deployment', value='docker-compose'),
        #     UserMemoryNode(type='constraint', key='deployment', value='Kubernetes')]
    """

    def extract(self, user_id: str, text: str) -> List[UserMemoryNode]:
        """Extract all user memory nodes from a piece of text."""
        nodes: List[UserMemoryNode] = []
        seen_keys: set = set()

        for sentence in self._split_sentences(text):
            sentence = sentence.strip()
            if not sentence:
                continue
            extracted = self._match_rules(user_id, sentence)
            for node in extracted:
                if node.key not in seen_keys:
                    seen_keys.add(node.key)
                    nodes.append(node)

        logger.debug("extractor: %d nodes from %d chars", len(nodes), len(text))
        return nodes

    def _split_sentences(self, text: str) -> List[str]:
        parts = re.split(r"[.!?;\n]", text)
        return [p.strip() for p in parts if p.strip()]

    def _match_rules(self, user_id: str, sentence: str) -> List[UserMemoryNode]:
        nodes = []
        sentence_lower = sentence.lower()

        for pattern, mem_type, confidence in _RULES:
            m = re.search(pattern, sentence_lower, re.IGNORECASE)
            if not m:
                continue

            if mem_type == "environment" and len(m.groups()) == 2:
                key_raw = m.group(1).strip()
                # Use original case for value by re-matching against original sentence
                m_orig = re.search(pattern, sentence, re.IGNORECASE)
                value_raw = (m_orig.group(2) if m_orig else m.group(2)).strip()
                # Only store if key looks like an env keyword
                key_normalized = key_raw.lower().replace(" ", "_")
                if key_normalized not in _ENV_KEYS and len(key_raw) > 20:
                    continue
                key = key_normalized
                value = self._clean_value(value_raw)
                if not value or value.lower() in _NOISE_VALUES:
                    continue
                nodes.append(UserMemoryNode(
                    user_id=user_id,
                    type="environment",
                    key=key,
                    value=value,
                    confidence=confidence,
                    source="agent_inferred",
                ))
            else:
                raw_value = m.group(1).strip() if m.groups() else sentence
                value = self._clean_value(raw_value)
                if not value or value in _NOISE_VALUES or len(value) < 2:
                    continue
                # Derive a short key from the value
                key = self._derive_key(value, mem_type)
                nodes.append(UserMemoryNode(
                    user_id=user_id,
                    type=mem_type,
                    key=key,
                    value=value,
                    confidence=confidence,
                    source="agent_inferred",
                ))
            break  # first matching rule wins per sentence

        return nodes

    def _clean_value(self, raw: str) -> str:
        # Remove trailing punctuation, articles, filler
        raw = re.sub(r"[.!?,;:]+$", "", raw).strip()
        raw = re.sub(r"^(?:the|a|an)\s+", "", raw, flags=re.IGNORECASE)
        return raw[:200]  # cap length

    def _derive_key(self, value: str, mem_type: str) -> str:
        """Produce a short snake_case key from the value."""
        # Take first meaningful word(s)
        words = re.findall(r"[a-zA-Z0-9\-]+", value)
        if not words:
            return mem_type
        key = "_".join(words[:3]).lower()
        return key[:50]
