"""
MundiX CLI - Collective Memory System
Local + Sync memory that learns from all users.
Uses SQLite for local storage and a simple similarity search for RAG.
"""

import json
import os
import hashlib
import time
from datetime import datetime
from pathlib import Path

MUNDIX_DIR = Path.home() / ".mundix"
MEMORY_FILE = MUNDIX_DIR / "memory.json"
COLLECTIVE_FILE = MUNDIX_DIR / "collective_memory.json"
CONFIG_FILE = MUNDIX_DIR / "config.json"


class Memory:
    """Manages local and collective memory for MundiX CLI."""

    def __init__(self):
        self._ensure_dirs()
        self.local_memory = self._load_json(MEMORY_FILE, default={"entries": [], "stats": {"total_queries": 0, "total_commands": 0}})
        self.collective_memory = self._load_json(COLLECTIVE_FILE, default={"entries": [], "version": "1.0.0", "last_sync": None})

    def _ensure_dirs(self):
        """Create MundiX config directory if it doesn't exist."""
        MUNDIX_DIR.mkdir(parents=True, exist_ok=True)

    def _load_json(self, path: Path, default: dict = None) -> dict:
        """Load a JSON file or return default."""
        try:
            if path.exists():
                with open(path, "r") as f:
                    return json.load(f)
        except (json.JSONDecodeError, IOError):
            pass
        return default or {}

    def _save_json(self, path: Path, data: dict):
        """Save data to a JSON file."""
        try:
            with open(path, "w") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
        except IOError as e:
            print(f"[!] Failed to save memory: {e}")

    def _hash_text(self, text: str) -> str:
        """Create a hash for deduplication."""
        return hashlib.md5(text.lower().strip().encode()).hexdigest()[:12]

    def _simple_similarity(self, query: str, text: str) -> float:
        """Simple keyword-based similarity score (0-1)."""
        query_words = set(query.lower().split())
        text_words = set(text.lower().split())
        if not query_words:
            return 0.0
        intersection = query_words & text_words
        return len(intersection) / len(query_words)

    def save_interaction(self, query: str, response: str, commands_executed: list = None,
                         was_useful: bool = True, tags: list = None):
        """Save an interaction to local memory."""
        entry = {
            "id": self._hash_text(query + str(time.time())),
            "timestamp": datetime.now().isoformat(),
            "query": query,
            "response": response[:2000],  # Truncate long responses
            "commands": commands_executed or [],
            "useful": was_useful,
            "tags": tags or [],
            "upvotes": 1 if was_useful else 0,
        }

        self.local_memory["entries"].append(entry)
        self.local_memory["stats"]["total_queries"] += 1
        if commands_executed:
            self.local_memory["stats"]["total_commands"] += len(commands_executed)

        self._save_json(MEMORY_FILE, self.local_memory)
        return entry["id"]

    def search_memory(self, query: str, top_k: int = 3, min_score: float = 0.3) -> list:
        """Search both local and collective memory for similar past interactions."""
        results = []

        # Search local memory
        for entry in self.local_memory.get("entries", []):
            score = self._simple_similarity(query, entry["query"])
            if score >= min_score:
                results.append({
                    "source": "local",
                    "score": score,
                    "query": entry["query"],
                    "response": entry["response"],
                    "commands": entry.get("commands", []),
                    "upvotes": entry.get("upvotes", 0),
                })

        # Search collective memory
        for entry in self.collective_memory.get("entries", []):
            score = self._simple_similarity(query, entry.get("query", ""))
            if score >= min_score:
                results.append({
                    "source": "collective",
                    "score": score,
                    "query": entry["query"],
                    "response": entry.get("response", ""),
                    "commands": entry.get("commands", []),
                    "upvotes": entry.get("upvotes", 0),
                })

        # Sort by score (descending), then by upvotes
        results.sort(key=lambda x: (x["score"], x["upvotes"]), reverse=True)
        return results[:top_k]

    def format_memory_context(self, results: list) -> str:
        """Format memory search results as context for the AI."""
        if not results:
            return ""

        context_parts = []
        for i, r in enumerate(results, 1):
            source_label = "Community" if r["source"] == "collective" else "Your History"
            context_parts.append(
                f"[{source_label}] (relevance: {r['score']:.0%}, votes: {r['upvotes']})\n"
                f"Q: {r['query']}\n"
                f"A: {r['response'][:500]}"
            )
            if r["commands"]:
                context_parts.append(f"Commands: {', '.join(r['commands'][:5])}")

        return "\n---\n".join(context_parts)

    def upvote_entry(self, entry_id: str):
        """Upvote a memory entry (mark as useful)."""
        for entry in self.local_memory.get("entries", []):
            if entry["id"] == entry_id:
                entry["upvotes"] = entry.get("upvotes", 0) + 1
                self._save_json(MEMORY_FILE, self.local_memory)
                return True
        return False

    def get_entries_for_sync(self, min_upvotes: int = 2) -> list:
        """Get high-quality entries to share with collective memory."""
        shareable = []
        for entry in self.local_memory.get("entries", []):
            if entry.get("upvotes", 0) >= min_upvotes and entry.get("useful", True):
                # Anonymize before sharing
                shareable.append({
                    "query": entry["query"],
                    "response": entry["response"],
                    "commands": entry.get("commands", []),
                    "tags": entry.get("tags", []),
                    "upvotes": entry.get("upvotes", 0),
                })
        return shareable

    def sync_collective(self, api_url: str = None):
        """
        Sync with collective memory server.
        In the prototype, this simulates the sync by merging local high-quality entries.
        In production, this would POST to a MundiX API and GET updated collective memory.
        """
        if api_url:
            # TODO: Implement real API sync
            # POST shareable entries to api_url/memory/contribute
            # GET updated collective from api_url/memory/collective
            pass
        else:
            # Prototype: merge high-quality local entries into collective
            shareable = self.get_entries_for_sync(min_upvotes=1)
            existing_queries = {e.get("query", "") for e in self.collective_memory.get("entries", [])}

            new_count = 0
            for entry in shareable:
                if entry["query"] not in existing_queries:
                    self.collective_memory["entries"].append(entry)
                    existing_queries.add(entry["query"])
                    new_count += 1

            self.collective_memory["last_sync"] = datetime.now().isoformat()
            self._save_json(COLLECTIVE_FILE, self.collective_memory)
            return new_count

    def get_stats(self) -> dict:
        """Return memory statistics."""
        return {
            "local_entries": len(self.local_memory.get("entries", [])),
            "collective_entries": len(self.collective_memory.get("entries", [])),
            "total_queries": self.local_memory.get("stats", {}).get("total_queries", 0),
            "total_commands": self.local_memory.get("stats", {}).get("total_commands", 0),
            "last_sync": self.collective_memory.get("last_sync"),
        }

    def clear_local(self):
        """Clear local memory."""
        self.local_memory = {"entries": [], "stats": {"total_queries": 0, "total_commands": 0}}
        self._save_json(MEMORY_FILE, self.local_memory)
