"""Memory storage backend - key-value persistence for agent state.

Uses file-based JSON storage with in-memory caching.
Falls back gracefully if file storage fails.

Private module (prefixed with _) — not auto-discovered as a tool.
"""

import json
import logging
from pathlib import Path
from typing import Any

from core.config import get_settings

logger = logging.getLogger(__name__)

# Storage location — resolved from centralised Settings (reads ALBUS_STORE_PATH).
_STORE_PATH = Path(get_settings().albus_store_path)
_MEMORY_DIR = _STORE_PATH / "memory"


def _ensure_dir() -> None:
    """Ensure memory directory exists."""
    _MEMORY_DIR.mkdir(parents=True, exist_ok=True)


def _ns_path(namespace: str) -> Path:
    """Get path for a namespace file."""
    safe_ns = "".join(c if c.isalnum() or c in "-_" else "_" for c in namespace)
    return _MEMORY_DIR / f"{safe_ns}.json"


def _load_namespace(namespace: str) -> dict[str, Any]:
    """Load a namespace from disk."""
    path = _ns_path(namespace)
    if path.exists():
        try:
            return json.loads(path.read_text())
        except Exception as e:
            logger.warning(f"Failed to load namespace {namespace}: {e}")
    return {}


def _save_namespace(namespace: str, data: dict[str, Any]) -> None:
    """Save a namespace to disk."""
    _ensure_dir()
    path = _ns_path(namespace)
    try:
        path.write_text(json.dumps(data, indent=2, default=str))
    except Exception as e:
        logger.warning(f"Failed to save namespace {namespace}: {e}")


# In-memory cache (loaded from disk on first access)
_CACHE: dict[str, dict[str, Any]] = {}


def get_namespace(namespace: str) -> dict[str, Any]:
    """Get namespace data (loads from disk if not cached)."""
    if namespace not in _CACHE:
        _CACHE[namespace] = _load_namespace(namespace)
    return _CACHE[namespace]


def set_value(namespace: str, key: str, value: Any) -> None:
    """Set a value and persist to disk."""
    ns = get_namespace(namespace)
    ns[key] = value
    _save_namespace(namespace, ns)


def get_value(namespace: str, key: str) -> tuple[bool, Any]:
    """Get a value. Returns (found, value)."""
    ns = get_namespace(namespace)
    if key in ns:
        return True, ns[key]
    return False, None


def search_namespace(namespace: str, query: str, limit: int = 10) -> list[dict]:
    """Search namespace for matching entries.

    Uses substring matching first (highest score), then falls back to
    fuzzy matching via ``difflib.SequenceMatcher`` for near-misses.
    """
    from difflib import SequenceMatcher

    ns = get_namespace(namespace)
    query_lower = query.lower()

    fuzzy_threshold = 0.4

    results: list[dict] = []
    for key, value in ns.items():
        value_str = str(value).lower()
        key_lower = key.lower()

        # Exact substring match — highest priority.
        if query_lower in key_lower:
            results.append({"key": key, "value": value, "score": 1.0})
        elif query_lower in value_str:
            results.append({"key": key, "value": value, "score": 0.7})
        else:
            # Fuzzy match against key and value.
            key_ratio = SequenceMatcher(None, query_lower, key_lower).ratio()
            val_ratio = SequenceMatcher(None, query_lower, value_str[:200]).ratio()
            best = max(key_ratio, val_ratio)
            if best >= fuzzy_threshold:
                results.append({"key": key, "value": value, "score": round(best * 0.5, 3)})

    results.sort(key=lambda x: x["score"], reverse=True)
    return results[:limit]
