"""
Phase 65: Memory Mapper — Ephemeral vs persistent memory mapping.

Three persistence levels:
  - ephemeral: in-memory only, lost on process exit
  - session: survives restart (disk-backed), respects TTL
  - persistent: permanent disk storage, respects TTL

Storage:
  state/broker/memory_map/
    persistent.json — persistent entries
    session.json    — session entries
"""

import json
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

from brynq.server import BROKER_DIR, _write_json_sync

# ── Paths ──────────────────────────────────────────────────────────────────

MEMORY_MAP_DIR = BROKER_DIR / "memory_map"
MEMORY_MAP_DIR.mkdir(parents=True, exist_ok=True)

PERSISTENT_PATH = MEMORY_MAP_DIR / "persistent.json"
SESSION_PATH = MEMORY_MAP_DIR / "session.json"

VALID_PERSISTENCE = ("ephemeral", "session", "persistent")

# ── In-memory store for ephemeral data ─────────────────────────────────────

_ephemeral: Dict[str, Dict[str, dict]] = {}  # scope -> key -> entry


# ── Disk helpers ───────────────────────────────────────────────────────────

def _load_disk_store(path: Path) -> dict:
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text("utf-8"))
    except (json.JSONDecodeError, OSError):
        return {}


def _save_disk_store(path: Path, data: dict) -> None:
    _write_json_sync(path, data)


def _disk_path(persistence: str) -> Path:
    if persistence == "persistent":
        return PERSISTENT_PATH
    return SESSION_PATH


def _make_entry(key: str, value: Any, persistence: str, ttl_hours: Optional[float],
                scope: str) -> dict:
    now = time.time()
    entry = {
        "key": key,
        "value": value,
        "persistence": persistence,
        "scope": scope,
        "created_at": now,
        "updated_at": now,
        "expires_at": now + (ttl_hours * 3600) if ttl_hours else None,
    }
    return entry


def _is_expired(entry: dict) -> bool:
    exp = entry.get("expires_at")
    if exp is None:
        return False
    return time.time() > exp


def _scoped_key(scope: str, key: str) -> str:
    return f"{scope}::{key}"


# ── Public API ─────────────────────────────────────────────────────────────

def store(key: str, value: Any, persistence: str = "persistent",
          ttl_hours: Optional[float] = None, scope: str = "global") -> dict:
    """Store a value with the given persistence level.

    Args:
        key: Storage key.
        value: Any JSON-serializable value.
        persistence: "ephemeral", "session", or "persistent".
        ttl_hours: Time-to-live in hours (None = no expiry).
        scope: Namespace scope (e.g., "global", "agent-1").

    Returns:
        The stored entry metadata.
    """
    if not key or not key.strip():
        raise ValueError("Key cannot be empty")
    if persistence not in VALID_PERSISTENCE:
        raise ValueError(f"Invalid persistence '{persistence}'. Must be one of: {VALID_PERSISTENCE}")
    if ttl_hours is not None and ttl_hours <= 0:
        raise ValueError("ttl_hours must be positive")

    entry = _make_entry(key, value, persistence, ttl_hours, scope)

    if persistence == "ephemeral":
        _ephemeral.setdefault(scope, {})[key] = entry
    else:
        path = _disk_path(persistence)
        data = _load_disk_store(path)
        sk = _scoped_key(scope, key)
        data[sk] = entry
        _save_disk_store(path, data)

    return {"key": key, "scope": scope, "persistence": persistence,
            "expires_at": entry["expires_at"]}


def get(key: str, scope: str = "global") -> Optional[Any]:
    """Retrieve a value. Checks ephemeral -> session -> persistent.

    Returns None if not found or expired.
    """
    # Check ephemeral first
    if scope in _ephemeral and key in _ephemeral[scope]:
        entry = _ephemeral[scope][key]
        if _is_expired(entry):
            del _ephemeral[scope][key]
            return None
        return entry["value"]

    # Check disk stores (session then persistent)
    sk = _scoped_key(scope, key)
    for path in (SESSION_PATH, PERSISTENT_PATH):
        data = _load_disk_store(path)
        if sk in data:
            entry = data[sk]
            if _is_expired(entry):
                del data[sk]
                _save_disk_store(path, data)
                return None
            return entry["value"]

    return None


def delete(key: str, scope: str = "global") -> bool:
    """Delete a key from all persistence levels. Returns True if found."""
    found = False

    # Ephemeral
    if scope in _ephemeral and key in _ephemeral[scope]:
        del _ephemeral[scope][key]
        found = True

    # Disk stores
    sk = _scoped_key(scope, key)
    for path in (SESSION_PATH, PERSISTENT_PATH):
        data = _load_disk_store(path)
        if sk in data:
            del data[sk]
            _save_disk_store(path, data)
            found = True

    return found


def list_keys(scope: Optional[str] = None, persistence: Optional[str] = None) -> List[dict]:
    """List all stored keys, optionally filtered by scope and persistence."""
    results = []

    # Ephemeral
    if persistence is None or persistence == "ephemeral":
        for s, entries in _ephemeral.items():
            if scope is not None and s != scope:
                continue
            for k, entry in entries.items():
                if not _is_expired(entry):
                    results.append({
                        "key": k, "scope": s, "persistence": "ephemeral",
                        "expires_at": entry.get("expires_at"),
                    })

    # Disk stores
    for level, path in [("session", SESSION_PATH), ("persistent", PERSISTENT_PATH)]:
        if persistence is not None and persistence != level:
            continue
        data = _load_disk_store(path)
        for sk, entry in data.items():
            s = entry.get("scope", "global")
            if scope is not None and s != scope:
                continue
            if not _is_expired(entry):
                results.append({
                    "key": entry.get("key", sk), "scope": s,
                    "persistence": level,
                    "expires_at": entry.get("expires_at"),
                })

    return results


def cleanup_expired() -> dict:
    """Remove all TTL-expired entries. Returns count removed per level."""
    removed = {"ephemeral": 0, "session": 0, "persistent": 0}

    # Ephemeral
    for s in list(_ephemeral.keys()):
        expired_keys = [k for k, e in _ephemeral[s].items() if _is_expired(e)]
        for k in expired_keys:
            del _ephemeral[s][k]
            removed["ephemeral"] += 1

    # Disk
    for level, path in [("session", SESSION_PATH), ("persistent", PERSISTENT_PATH)]:
        data = _load_disk_store(path)
        expired = [sk for sk, e in data.items() if _is_expired(e)]
        for sk in expired:
            del data[sk]
            removed[level] += 1
        if expired:
            _save_disk_store(path, data)

    return removed


def get_memory_map() -> dict:
    """Overview of all stored data by scope and persistence level."""
    summary: Dict[str, Dict[str, int]] = {}

    # Ephemeral
    for s, entries in _ephemeral.items():
        live = sum(1 for e in entries.values() if not _is_expired(e))
        if live:
            summary.setdefault(s, {})["ephemeral"] = live

    # Disk
    for level, path in [("session", SESSION_PATH), ("persistent", PERSISTENT_PATH)]:
        data = _load_disk_store(path)
        for sk, entry in data.items():
            if _is_expired(entry):
                continue
            s = entry.get("scope", "global")
            summary.setdefault(s, {})[level] = summary.get(s, {}).get(level, 0) + 1

    total = sum(v for scope_data in summary.values() for v in scope_data.values())
    return {"scopes": summary, "total_entries": total}
