"""
User Memory Ranker — Workflow alignment scoring using User Memory.

PRD section 10: Workflow Ranking Fusion.

Final score formula:
    final_score = workflow_score * 0.7 + user_alignment_score * 0.3

User alignment score:
    Checks if workflow tools/stack match user preferences & environment.
    Penalizes if workflow contains constrained items.
"""

import re
import logging
from typing import List, Dict, Optional
from openmemo.user_memory.user_memory_store import UserMemoryNode, UserMemoryStore
from openmemo.user_memory.user_memory_recall import UserMemoryRecall

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


class UserMemoryRanker:
    """
    Adjusts workflow/experience scores using User Memory alignment.

    Usage:
        ranker = UserMemoryRanker(store)

        workflows = [
            {"id": "wf1", "steps": ["docker-compose up", "nginx start"], "score": 0.8},
            {"id": "wf2", "steps": ["kubectl apply", "helm install"], "score": 0.75},
        ]

        ranked = ranker.rank_workflows("alice", workflows)
        # wf1 gets boosted (docker-compose matches preference)
        # wf2 gets penalized (kubernetes matches constraint)
    """

    WORKFLOW_WEIGHT = 0.7
    USER_WEIGHT = 0.3

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

    def rank_workflows(
        self,
        user_id: str,
        workflows: List[Dict],
        score_key: str = "score",
    ) -> List[Dict]:
        """
        Re-rank a list of workflow dicts using user alignment.

        Each workflow dict must have a `score` field (0-1).
        Returns the list sorted by final_score descending.
        """
        if not workflows:
            return []

        by_type = self.recall.recall_all_types(user_id, top_n_per_type=10)
        preferences = by_type.get("preference", [])
        constraints = by_type.get("constraint", [])
        environment = by_type.get("environment", [])

        results = []
        for wf in workflows:
            base = float(wf.get(score_key, 0.5))
            align = self._alignment_score(wf, preferences, constraints, environment)
            final = base * self.WORKFLOW_WEIGHT + align * self.USER_WEIGHT
            results.append({**wf, "user_alignment": round(align, 3),
                             "final_score": round(final, 3)})

        results.sort(key=lambda x: x["final_score"], reverse=True)
        logger.debug("ranker: ranked %d workflows for user=%s", len(results), user_id)
        return results

    def compute_alignment(
        self,
        user_id: str,
        workflow_text: str,
    ) -> float:
        """
        Compute alignment score (0-1) for free-form workflow text.
        Useful for scoring a single workflow description.
        """
        by_type = self.recall.recall_all_types(user_id, top_n_per_type=10)
        wf = {"steps": [workflow_text], "description": workflow_text}
        return self._alignment_score(
            wf,
            by_type.get("preference", []),
            by_type.get("constraint", []),
            by_type.get("environment", []),
        )

    # ── Internal ───────────────────────────────────────────────────────────────

    def _alignment_score(
        self,
        workflow: Dict,
        preferences: List[UserMemoryNode],
        constraints: List[UserMemoryNode],
        environment: List[UserMemoryNode],
    ) -> float:
        """
        Compute alignment score in [0, 1].

        +0.2 per preference match (cap at 0.6)
        -0.3 per constraint match (floor 0)
        +0.1 per environment match (cap at 0.2)
        """
        text = self._workflow_text(workflow).lower()

        boost = 0.0
        penalty = 0.0

        for pref in preferences:
            if self._token_match(pref.value, text):
                boost += 0.2 * pref.confidence
        boost = min(boost, 0.6)

        for con in constraints:
            if self._token_match(con.value, text):
                penalty += 0.3 * con.confidence
        penalty = min(penalty, 0.9)

        env_boost = 0.0
        for env in environment:
            if self._token_match(env.value, text):
                env_boost += 0.1 * env.confidence
        env_boost = min(env_boost, 0.2)

        score = 0.5 + boost + env_boost - penalty  # base 0.5
        return max(0.0, min(1.0, score))

    def _workflow_text(self, workflow: Dict) -> str:
        parts = []
        for key in ("description", "name", "title", "summary"):
            if workflow.get(key):
                parts.append(str(workflow[key]))
        steps = workflow.get("steps") or workflow.get("workflow") or []
        if isinstance(steps, list):
            parts.extend(str(s) for s in steps)
        elif isinstance(steps, str):
            parts.append(steps)
        return " ".join(parts)

    def _token_match(self, value: str, text: str) -> bool:
        """True if any meaningful token from value appears in text."""
        tokens = re.findall(r"[a-z0-9][\w\-\.]*", value.lower())
        return any(t in text for t in tokens if len(t) >= 3)
