"""
Receipt → Mem0 Ingestion Module.

This module is the connective tissue between the deterministic receipt system
and the probabilistic semantic memory layer (Mem0).

Every receipt that passes through the writer gets:
1. Canonicalized (JCS RFC 8785)
2. Hashed (SHA-256)
3. Embedded into Mem0 with constitutional metadata

This enables:
- Semantic retrieval of past decisions
- Constitutional weighting of memories (dignity, coherence, importance)
- Lineage-aware context building
- Value-weighted remembrance

Usage:
    from receipts.mem0_ingest import ingest_receipt, search_receipts

    # Ingest a receipt into memory
    await ingest_receipt(receipt)

    # Search with constitutional weighting
    results = await search_receipts("guardian veto decisions", space="governance")
"""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional
from datetime import datetime

from receipts.canonicalize import to_jcs_bytes, compute_payload_hash

logger = logging.getLogger(__name__)

# Lazy-load Mem0 to avoid import errors if not installed
_mem0_client: Optional[Any] = None


def _get_mem0() -> Any:
    """Lazy-load Mem0 client."""
    global _mem0_client
    if _mem0_client is None:
        try:
            from mem0 import Memory
            _mem0_client = Memory()
            logger.info("[mem0_ingest] Mem0 client initialized")
        except ImportError:
            logger.warning("[mem0_ingest] Mem0 not installed, using stub")
            _mem0_client = _Mem0Stub()
    return _mem0_client


class _Mem0Stub:
    """Stub for when Mem0 is not installed."""

    def add(self, *args, **kwargs) -> Dict[str, Any]:
        logger.debug("[mem0_ingest] Stub: add called")
        return {"id": "stub", "status": "stub"}

    def search(self, *args, **kwargs) -> List[Dict[str, Any]]:
        logger.debug("[mem0_ingest] Stub: search called")
        return []

    def get_all(self, *args, **kwargs) -> List[Dict[str, Any]]:
        return []


def extract_constitutional_metadata(receipt: Any) -> Dict[str, Any]:
    """
    Extract constitutional metadata from a receipt for Mem0 storage.

    This metadata enables constitutional reranking during retrieval.
    """
    # Handle both Pydantic models and dicts
    if hasattr(receipt, "model_dump"):
        data = receipt.model_dump(mode="json")
    elif hasattr(receipt, "dict"):
        data = receipt.dict()
    elif isinstance(receipt, dict):
        data = receipt
    else:
        data = {}

    # Extract coherence signals if present
    coherence = data.get("coherence", {})
    if isinstance(coherence, dict):
        coherence_score = coherence.get("coherence_score", 0.5)
        dignity = coherence.get("alignment", 0.5)
        confidence = coherence.get("confidence", 0.5)
    else:
        coherence_score = 0.5
        dignity = 0.5
        confidence = 0.5

    return {
        # Core identifiers
        "receipt_id": data.get("receipt_id", data.get("id", "")),
        "receipt_type": data.get("receipt_type", data.get("type", "unknown")),
        "actor_type": data.get("actor_type", "unknown"),
        "actor_id": data.get("actor_id", ""),
        "event_type": data.get("event_type", ""),
        "domain": data.get("domain", "default"),
        "space": data.get("space", "default"),

        # Constitutional signals
        "coherence_score": coherence_score,
        "dignity": dignity,
        "confidence": confidence,
        "importance": data.get("importance", 0.3),
        "surprise": data.get("surprise", 0.1),
        "alignment": data.get("alignment", dignity),

        # Lineage
        "parent_id": data.get("parent_id", data.get("parentId", "")),
        "lineage_depth": data.get("lineage_depth", data.get("lineageDepth", 0)),
        "root_id": data.get("root_id", data.get("rootId", "")),

        # Guardian/Governor signals
        "guardian_verdict": data.get("guardian_verdict", ""),
        "dignity_score": data.get("dignity_score", dignity),

        # Timestamp
        "issued_at": data.get("issued_at", data.get("issuedAt", datetime.utcnow().isoformat())),
    }


def ingest_receipt(receipt: Any, space: Optional[str] = None) -> Dict[str, Any]:
    """
    Ingest a receipt into Mem0 with constitutional metadata.

    Args:
        receipt: Receipt object (Pydantic model or dict)
        space: Optional space override (defaults to receipt's space)

    Returns:
        Mem0 add() result
    """
    mem0 = _get_mem0()

    # Canonicalize
    canonical_bytes = to_jcs_bytes(receipt)
    canonical_text = canonical_bytes.decode("utf-8")

    # Compute hash
    receipt_hash = compute_payload_hash(receipt)

    # Extract metadata
    metadata = extract_constitutional_metadata(receipt)
    metadata["receipt_hash"] = receipt_hash
    metadata["canonical_length"] = len(canonical_bytes)

    # Determine space/user_id
    effective_space = space or metadata.get("space", "default")
    user_id = f"space:{effective_space}"

    # Ingest into Mem0
    # Mem0 client APIs vary between versions. Inspect the `add` signature and adapt.
    try:
        import inspect

        add_func = getattr(mem0, "add")
        try:
            sig = inspect.signature(add_func)
            params = [p.name for p in sig.parameters.values() if p.name != "self"]
        except Exception:
            params = []

        # Preferred keyword names
        if "text" in params:
            result = add_func(text=canonical_text, metadata=metadata, user_id=user_id)
        elif "content" in params:
            result = add_func(content=canonical_text, metadata=metadata, user_id=user_id)
        elif "metadata" in params and params[0] == "metadata":
            # signature like add(metadata, ...)
            result = add_func(metadata, canonical_text, user_id)
        else:
            # Fallback: try positional (text, metadata, user_id)
            result = add_func(canonical_text, metadata, user_id)

        logger.debug(f"[mem0_ingest] Ingested receipt {metadata['receipt_id']} into {user_id}")
        return result
    except TypeError as e:
        logger.error(f"[mem0_ingest] Failed to ingest receipt (mem0 API mismatch): {e}")
        return {"error": str(e), "receipt_id": metadata.get("receipt_id")}
    except Exception as e:
        logger.error(f"[mem0_ingest] Failed to ingest receipt: {e}")
        return {"error": str(e), "receipt_id": metadata.get("receipt_id")}


def search_receipts(
    query: str,
    space: str = "default",
    limit: int = 20,
    constitutional_rerank: bool = True,
) -> List[Dict[str, Any]]:
    """
    Search receipts with optional constitutional reranking.

    Args:
        query: Semantic search query
        space: Space to search in
        limit: Max results
        constitutional_rerank: If True, rerank by constitutional score

    Returns:
        List of matching memories with constitutional scores
    """
    mem0 = _get_mem0()
    user_id = f"space:{space}"

    try:
        # Get raw results from Mem0
        raw_results = mem0.search(query, user_id=user_id, limit=limit * 2 if constitutional_rerank else limit)

        if not constitutional_rerank:
            return raw_results[:limit]

        # Constitutional reranking
        ranked = []
        for result in raw_results:
            meta = result.get("metadata", {})
            semantic_score = result.get("score", 0.5)

            # Compute constitutional score
            constitutional_score = compute_constitutional_score(semantic_score, meta)

            ranked.append({
                **result,
                "constitutional_score": constitutional_score,
                "semantic_score": semantic_score,
            })

        # Sort by constitutional score
        ranked.sort(key=lambda x: x["constitutional_score"], reverse=True)

        return ranked[:limit]

    except Exception as e:
        logger.error(f"[mem0_ingest] Search failed: {e}")
        return []


def compute_constitutional_score(semantic_score: float, metadata: Dict[str, Any]) -> float:
    """
    Compute constitutional relevance score.

    This implements value-weighted memory retrieval:
    - High dignity memories surface first
    - High coherence memories are more trusted
    - Important memories get priority
    - Deep lineage is penalized (prefer root insights)
    """
    dignity = metadata.get("dignity", 0.5)
    coherence = metadata.get("coherence_score", 0.5)
    importance = metadata.get("importance", 0.3)
    lineage_depth = metadata.get("lineage_depth", 0)

    # Lineage penalty: prefer root receipts over deep descendants
    lineage_penalty = 1.0 / (1.0 + lineage_depth * 0.1)

    # Constitutional score formula
    score = (
        semantic_score
        * (0.3 + dignity * 0.7)  # Dignity floor of 0.3
        * (0.3 + coherence * 0.7)  # Coherence floor of 0.3
        * (0.2 + importance * 0.8)  # Importance floor of 0.2
        * lineage_penalty
    )

    return score


def get_memory_stats(space: str = "default") -> Dict[str, Any]:
    """Get memory statistics for a space."""
    mem0 = _get_mem0()
    user_id = f"space:{space}"

    try:
        all_memories = mem0.get_all(user_id=user_id)
        if not all_memories:
            return {"count": 0, "space": space}

        # Aggregate stats
        receipt_types: Dict[str, int] = {}
        total_dignity = 0.0
        total_coherence = 0.0

        for mem in all_memories:
            meta = mem.get("metadata", {})
            rt = meta.get("receipt_type", "unknown")
            receipt_types[rt] = receipt_types.get(rt, 0) + 1
            total_dignity += meta.get("dignity", 0.5)
            total_coherence += meta.get("coherence_score", 0.5)

        count = len(all_memories)

        return {
            "count": count,
            "space": space,
            "receipt_types": receipt_types,
            "mean_dignity": total_dignity / count if count > 0 else 0.0,
            "mean_coherence": total_coherence / count if count > 0 else 0.0,
        }

    except Exception as e:
        logger.error(f"[mem0_ingest] get_memory_stats failed: {e}")
        return {"count": 0, "space": space, "error": str(e)}


__all__ = [
    "ingest_receipt",
    "search_receipts",
    "compute_constitutional_score",
    "extract_constitutional_metadata",
    "get_memory_stats",
]
