"""
User Memory Recall — Retrieves and ranks user memory nodes for injection.

PRD section 8: Recall API, ranking formula, output format.

Ranking formula:
    score = confidence * 0.5 + recency * 0.3 + usage_count * 0.2

Output:
    User Preferences:
    - prefers docker-compose
    - prefers FastAPI

    Constraints:
    - avoid Kubernetes
"""

import time
import logging
from typing import List, Dict, Optional
from openmemo.user_memory.user_memory_store import UserMemoryNode, UserMemoryStore

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

_TYPE_ORDER = ["preference", "constraint", "environment", "behavior"]


class UserMemoryRecall:
    """
    Retrieves and ranks UserMemoryNodes for a given user.

    Usage:
        recall = UserMemoryRecall(store)
        result = recall.recall("alice", top_n=5)
        prompt_block = recall.format_for_prompt(result)
    """

    def __init__(self, store: UserMemoryStore):
        self.store = store

    def recall(
        self,
        user_id: str,
        top_n: int = 5,
        mem_type: str = "",
    ) -> List[UserMemoryNode]:
        """
        Retrieve and rank user memory nodes.

        Returns top_n nodes ranked by:
            score = confidence*0.5 + recency*0.3 + usage_norm*0.2
        """
        nodes = self.store.get_by_user(user_id, mem_type=mem_type)
        if not nodes:
            return []

        ranked = self._rank(nodes)

        # Increment usage for returned nodes
        for node in ranked[:top_n]:
            self.store.increment_usage(node.memory_id)

        logger.debug("recall user=%s total=%d returned=%d",
                     user_id, len(nodes), min(top_n, len(ranked)))
        return ranked[:top_n]

    def recall_all_types(
        self,
        user_id: str,
        top_n_per_type: int = 3,
    ) -> Dict[str, List[UserMemoryNode]]:
        """
        Recall top N nodes per memory type (preference/constraint/environment/behavior).
        """
        result = {}
        for t in _TYPE_ORDER:
            nodes = self.store.get_by_user(user_id, mem_type=t)
            result[t] = self._rank(nodes)[:top_n_per_type]
        return result

    def format_for_prompt(
        self,
        user_id: str,
        top_n: int = 5,
    ) -> str:
        """
        Format user memory as a prompt block for injection.

        Returns a string like:
            USER PROFILE
            - prefers FastAPI
            - prefers docker-compose
            - avoid Kubernetes
        """
        by_type = self.recall_all_types(user_id, top_n_per_type=top_n)

        lines = []
        label_map = {
            "preference":   "User Preferences",
            "constraint":   "Constraints",
            "environment":  "Environment",
            "behavior":     "Behavior",
        }

        for t in _TYPE_ORDER:
            nodes = by_type.get(t, [])
            if not nodes:
                continue
            lines.append(f"{label_map[t]}:")
            for node in nodes:
                if t == "preference":
                    lines.append(f"  - prefers {node.value}")
                elif t == "constraint":
                    lines.append(f"  - avoid {node.value}")
                elif t == "environment":
                    lines.append(f"  - {node.key}: {node.value}")
                else:
                    lines.append(f"  - {node.value}")

        if not lines:
            return ""

        return "USER PROFILE\n" + "\n".join(lines)

    def to_dict(self, user_id: str, top_n: int = 5) -> dict:
        """
        Return structured dict suitable for JSON API response.
        """
        by_type = self.recall_all_types(user_id, top_n_per_type=top_n)
        return {
            "user_id": user_id,
            "preferences": [n.to_dict() for n in by_type.get("preference", [])],
            "constraints": [n.to_dict() for n in by_type.get("constraint", [])],
            "environment": [n.to_dict() for n in by_type.get("environment", [])],
            "behavior":    [n.to_dict() for n in by_type.get("behavior", [])],
        }

    # ── Ranking ────────────────────────────────────────────────────────────────

    def _rank(self, nodes: List[UserMemoryNode]) -> List[UserMemoryNode]:
        if not nodes:
            return []

        now = time.time()
        # Recency: full score within 7 days, decays to 0 at 90 days
        max_age = 90 * 86400
        # Usage: normalize usage_count (cap at 20)
        max_usage = max((n.usage_count for n in nodes), default=1) or 1

        def score(n: UserMemoryNode) -> float:
            age = now - n.timestamp
            recency = max(0.0, 1.0 - age / max_age)
            usage_norm = min(n.usage_count, 20) / max(max_usage, 1)
            return n.confidence * 0.5 + recency * 0.3 + usage_norm * 0.2

        return sorted(nodes, key=score, reverse=True)
