"""
Tenant-specific persistent memory for AiCippy.

Each logged-in user has isolated memory storage that persists across sessions.
Stores user preferences, learned context, and workspace metadata.
"""

from __future__ import annotations

import json
from datetime import UTC, datetime
from pathlib import Path
from typing import TYPE_CHECKING, Any, Final

if TYPE_CHECKING:
    import asyncio

from aicippy.utils.logging import get_logger

logger = get_logger(__name__)

MEMORY_DIR_NAME: Final[str] = "tenant_memory"
MEMORY_FILE: Final[str] = "memory.json"
MAX_MEMORY_ENTRIES: Final[int] = 500


class TenantMemory:
    """
    Persistent key-value memory isolated per user/tenant.

    Stores:
    - User preferences (model, mode, theme)
    - Project context (last init results, tech stacks)
    - Learned patterns (frequently used commands, common queries)
    - Custom notes and bookmarks
    """

    __slots__ = ("_data", "_memory_dir", "_memory_file", "_user_id")

    def __init__(self, user_id: str, base_dir: Path | None = None) -> None:
        self._user_id = user_id

        base = base_dir or (Path.home() / ".aicippy")
        self._memory_dir = base / MEMORY_DIR_NAME / user_id
        self._memory_dir.mkdir(parents=True, exist_ok=True)

        self._memory_file = self._memory_dir / MEMORY_FILE
        self._data: dict[str, Any] = {}

        self._load()
        logger.debug(
            "tenant_memory_initialized",
            user_id=user_id,
            entries=len(self._data.get("entries", {})),
        )

    def _load(self) -> None:
        """Load memory from disk."""
        if self._memory_file.exists():
            try:
                self._data = json.loads(self._memory_file.read_text(encoding="utf-8"))
            except (json.JSONDecodeError, OSError) as e:
                logger.warning("tenant_memory_load_failed", error=str(e))
                self._data = {"entries": {}, "preferences": {}, "context": {}}
        else:
            self._data = {
                "user_id": self._user_id,
                "created_at": datetime.now(UTC).isoformat(),
                "entries": {},
                "preferences": {},
                "context": {},
            }

    def _save(self) -> None:
        """Persist memory to disk."""
        self._data["updated_at"] = datetime.now(UTC).isoformat()
        try:
            self._memory_file.write_text(
                json.dumps(self._data, ensure_ascii=False, indent=2),
                encoding="utf-8",
            )
        except OSError as e:
            logger.warning("tenant_memory_save_failed", error=str(e))

    def set(self, key: str, value: Any) -> None:
        """Store a key-value pair in memory."""
        entries = self._data.setdefault("entries", {})
        entries[key] = {
            "value": value,
            "updated_at": datetime.now(UTC).isoformat(),
        }
        # Enforce entry limit (remove oldest)
        if len(entries) > MAX_MEMORY_ENTRIES:
            sorted_keys = sorted(entries, key=lambda k: entries[k].get("updated_at", ""))
            for old_key in sorted_keys[: len(entries) - MAX_MEMORY_ENTRIES]:
                del entries[old_key]
        self._save()

    def get(self, key: str, default: Any = None) -> Any:
        """Retrieve a value from memory."""
        entry = self._data.get("entries", {}).get(key)
        if entry is None:
            return default
        return entry.get("value", default)

    def delete(self, key: str) -> bool:
        """Remove a key from memory."""
        entries = self._data.get("entries", {})
        if key in entries:
            del entries[key]
            self._save()
            return True
        return False

    def list_keys(self) -> list[str]:
        """List all memory keys."""
        return list(self._data.get("entries", {}).keys())

    def set_preference(self, key: str, value: Any) -> None:
        """Store a user preference."""
        self._data.setdefault("preferences", {})[key] = value
        self._save()

    def get_preference(self, key: str, default: Any = None) -> Any:
        """Get a user preference."""
        return self._data.get("preferences", {}).get(key, default)

    def set_context(self, key: str, value: Any) -> None:
        """Store workspace/project context."""
        self._data.setdefault("context", {})[key] = {
            "value": value,
            "updated_at": datetime.now(UTC).isoformat(),
        }
        self._save()

    def get_context(self, key: str, default: Any = None) -> Any:
        """Get workspace/project context."""
        entry = self._data.get("context", {}).get(key)
        if entry is None:
            return default
        return entry.get("value", default)

    def get_summary(self) -> str:
        """Get a text summary of stored memory for injection into prompts."""
        entries = self._data.get("entries", {})
        prefs = self._data.get("preferences", {})
        ctx = self._data.get("context", {})

        parts = []
        if prefs:
            parts.append("User Preferences: " + ", ".join(f"{k}={v}" for k, v in prefs.items()))
        if ctx:
            for k, v in ctx.items():
                parts.append(f"Context [{k}]: {json.dumps(v.get('value', ''), default=str)[:200]}")
        if entries:
            recent = sorted(
                entries.items(),
                key=lambda x: x[1].get("updated_at", ""),
                reverse=True,
            )[:10]
            for key, entry in recent:
                val_str = json.dumps(
                    entry.get("value", ""), default=str,
                )[:100]
                parts.append(f"Memory [{key}]: {val_str}")

        return "\n".join(parts) if parts else ""

    def clear(self) -> None:
        """Clear all tenant memory."""
        self._data = {
            "user_id": self._user_id,
            "created_at": self._data.get("created_at", datetime.now(UTC).isoformat()),
            "entries": {},
            "preferences": {},
            "context": {},
        }
        self._save()
        logger.info("tenant_memory_cleared", user_id=self._user_id)


class PlatformBackedTenantMemory:
    """Tenant memory backed by the AiVibe platform API.

    Writes via AppClient (api.aicippy.com) and reads from direct DB
    (if enabled) or API fallback. Falls back to local TenantMemory
    when the platform is unreachable.

    Args:
        user_id: User identifier for scoping memory.
        app_client: AppClient for api.aicippy.com.
        db_reader: Optional TenantDBReader for direct RDS reads.
        base_dir: Base directory for local fallback storage.
    """

    __slots__ = ("_app_client", "_background_tasks", "_db_reader", "_local", "_user_id")

    def __init__(
        self,
        user_id: str,
        app_client: Any,
        db_reader: Any = None,
        base_dir: Path | None = None,
    ) -> None:
        self._user_id = user_id
        self._app_client = app_client
        self._db_reader = db_reader
        self._background_tasks: set[asyncio.Task[None]] = set()
        # Local fallback always available
        self._local = TenantMemory(user_id=user_id, base_dir=base_dir)

    def set(self, key: str, value: Any) -> None:
        """Store a key-value pair (local + async platform save)."""
        self._local.set(key, value)
        self._save_to_platform_async(key, value)

    def get(self, key: str, default: Any = None) -> Any:
        """Retrieve a value from memory (local)."""
        return self._local.get(key, default)

    def delete(self, key: str) -> bool:
        """Remove a key from memory."""
        return self._local.delete(key)

    def list_keys(self) -> list[str]:
        """List all memory keys."""
        return self._local.list_keys()

    def set_preference(self, key: str, value: Any) -> None:
        """Store a user preference (local + async platform save)."""
        self._local.set_preference(key, value)
        self._save_to_platform_async(f"pref:{key}", value)

    def get_preference(self, key: str, default: Any = None) -> Any:
        """Get a user preference."""
        return self._local.get_preference(key, default)

    def set_context(self, key: str, value: Any) -> None:
        """Store workspace/project context (local + async platform save)."""
        self._local.set_context(key, value)
        self._save_to_platform_async(f"ctx:{key}", value)

    def get_context(self, key: str, default: Any = None) -> Any:
        """Get workspace/project context."""
        return self._local.get_context(key, default)

    def get_summary(self) -> str:
        """Get a text summary of stored memory for AI prompt injection."""
        return self._local.get_summary()

    def clear(self) -> None:
        """Clear all tenant memory."""
        self._local.clear()

    def _save_to_platform_async(self, key: str, value: Any) -> None:
        """Fire-and-forget save to platform API."""
        import asyncio

        try:
            loop = asyncio.get_running_loop()
            task = loop.create_task(self._save_to_platform(key, value))
            # Store reference to prevent garbage collection of the task (RUF006)
            self._background_tasks.add(task)
            task.add_done_callback(self._background_tasks.discard)
        except RuntimeError:
            # No running loop - skip platform save (local already saved)
            pass

    async def _save_to_platform(self, key: str, value: Any) -> None:
        """Save a memory entry to the platform API."""
        try:
            await self._app_client.save_memory(
                user_id=self._user_id,
                key=key,
                value=value,
            )
        except Exception as e:
            logger.warning("platform_memory_save_failed", key=key, error=str(e))
