"""
Lightweight in-memory feedback store for the V3 Inspector.

Tracks memory events across the adapter lifecycle:
  - Global impact metrics  (stored, recalled, workflows_reused, tokens_saved)
  - Per-task event traces  (recalled / stored / injected memories)
  - Per-task timelines     (ordered event stream)
  - Per-memory usage stats (usage_count, adoption_rate, quality_score)

No external dependencies — stdlib only.
"""

import hashlib
import time
from threading import Lock
from typing import List, Dict, Optional

AVG_WORKFLOW_TOKENS = 350


def _pseudo_id(content: str) -> str:
    return "m-" + hashlib.md5(content.encode("utf-8", errors="replace")).hexdigest()[:12]


class FeedbackStore:
    def __init__(self):
        self._lock = Lock()
        self._stored = 0
        self._recalled = 0
        self._workflows_reused = 0
        self._tasks_avoided = 0
        self._tokens_saved = 0
        self._task_traces: Dict[str, dict] = {}
        self._task_timelines: Dict[str, list] = {}
        self._memory_stats: Dict[str, dict] = {}

    def _ensure_task(self, task_id: str) -> dict:
        if task_id not in self._task_traces:
            self._task_traces[task_id] = {"recalled": [], "stored": [], "injected": []}
            self._task_timelines[task_id] = []
        return self._task_traces[task_id]

    def _add_timeline_event(self, task_id: str, event_type: str,
                            memory_id: str, summary: str):
        if task_id not in self._task_timelines:
            self._task_timelines[task_id] = []
        self._task_timelines[task_id].append({
            "ts": int(time.time() * 1000),
            "type": event_type,
            "memory_id": memory_id,
            "summary": summary[:120],
        })

    def on_store(self, content: str, memory_type: str = "observation",
                 task_id: str = "", memory_id: str = ""):
        mid = memory_id or _pseudo_id(content)
        summary = content[:120]
        with self._lock:
            self._stored += 1
            self._memory_stats.setdefault(mid, {
                "usage_count": 0, "adopted": 0, "last_used": 0,
                "summary": summary, "type": memory_type or "observation",
            })
            if task_id:
                trace = self._ensure_task(task_id)
                trace["stored"].append({"memory_id": mid, "summary": summary, "type": memory_type})
                self._add_timeline_event(task_id, "stored", mid, summary)

    def on_recall(self, summaries: List[str], task_id: str = "",
                  memory_ids: Optional[List[str]] = None):
        now = int(time.time() * 1000)
        with self._lock:
            self._recalled += len(summaries)
            for i, text in enumerate(summaries):
                mid = (memory_ids[i] if memory_ids and i < len(memory_ids)
                       else _pseudo_id(text))
                summary = text[:120]
                stats = self._memory_stats.setdefault(mid, {
                    "usage_count": 0, "adopted": 0, "last_used": 0,
                    "summary": summary, "type": "memory",
                })
                stats["usage_count"] += 1
                stats["last_used"] = now
                if task_id:
                    trace = self._ensure_task(task_id)
                    trace["recalled"].append({"memory_id": mid, "summary": summary, "type": "memory"})
                    self._add_timeline_event(task_id, "recalled", mid, summary)

    def on_inject(self, summaries: List[str], task_id: str = "",
                  memory_ids: Optional[List[str]] = None,
                  token_estimate: int = 0):
        with self._lock:
            self._tokens_saved += token_estimate or len(summaries) * 80
            for i, text in enumerate(summaries):
                mid = (memory_ids[i] if memory_ids and i < len(memory_ids)
                       else _pseudo_id(text))
                summary = text[:120]
                if task_id:
                    trace = self._ensure_task(task_id)
                    trace["injected"].append({"memory_id": mid, "summary": summary, "type": "memory"})
                    self._add_timeline_event(task_id, "injected", mid, summary)

    def on_workflow_reuse(self, task_id: str = ""):
        with self._lock:
            self._workflows_reused += 1
            self._tasks_avoided += 1
            self._tokens_saved += AVG_WORKFLOW_TOKENS
            if task_id:
                self._ensure_task(task_id)
                self._add_timeline_event(task_id, "workflow_reused", "", "pre-check matched")

    def on_adopt(self, content: str, task_id: str = "", memory_id: str = ""):
        mid = memory_id or _pseudo_id(content)
        with self._lock:
            if mid in self._memory_stats:
                self._memory_stats[mid]["adopted"] = self._memory_stats[mid].get("adopted", 0) + 1

    def get_metrics(self) -> dict:
        with self._lock:
            return {
                "memories_stored": self._stored,
                "memories_recalled": self._recalled,
                "workflows_reused": self._workflows_reused,
                "tasks_avoided": self._tasks_avoided,
                "tokens_saved": self._tokens_saved,
            }

    def get_task_trace(self, task_id: str) -> dict:
        with self._lock:
            trace = self._task_traces.get(
                task_id, {"recalled": [], "stored": [], "injected": []}
            )
            return {"task_id": task_id, **{k: list(v) for k, v in trace.items()}}

    def get_timeline(self, task_id: str) -> dict:
        with self._lock:
            events = self._task_timelines.get(task_id, [])
            return {"task_id": task_id, "events": list(events)}

    def get_memory_profile(self, memory_id: str) -> dict:
        with self._lock:
            stats = self._memory_stats.get(memory_id, {})
            usage = stats.get("usage_count", 0)
            adopted = stats.get("adopted", 0)
            adoption_rate = adopted / max(usage, 1)
            quality_score = min(
                0.4 * adoption_rate + 0.4 * min(usage / 5.0, 1.0) + 0.2,
                1.0,
            )
            return {
                "memory_id": memory_id,
                "summary": stats.get("summary", memory_id[:80]),
                "type": stats.get("type", "memory"),
                "memory_layer": stats.get("type", "memory"),
                "usage_count": usage,
                "adoption_rate": round(adoption_rate, 3),
                "quality_score": round(quality_score, 3),
                "last_used": stats.get("last_used", 0),
            }

    def get_top_memories(self, n: int = 10) -> List[dict]:
        with self._lock:
            items = []
            for mid, stats in self._memory_stats.items():
                usage = stats.get("usage_count", 0)
                if usage < 1:
                    continue
                adopted = stats.get("adopted", 0)
                adoption_rate = adopted / max(usage, 1)
                quality_score = min(
                    0.4 * adoption_rate + 0.4 * min(usage / 5.0, 1.0) + 0.2,
                    1.0,
                )
                items.append({
                    "memory_id": mid,
                    "summary": stats.get("summary", mid[:80]),
                    "type": stats.get("type", "memory"),
                    "memory_layer": stats.get("type", "memory"),
                    "quality_score": round(quality_score, 3),
                    "usage_count": usage,
                })
            items.sort(key=lambda x: x["quality_score"], reverse=True)
            return items[:n]
