#!/usr/bin/env python3
"""
Genesis Unified Memory API
==========================
Single FastAPI service for memory operations across all agents.
Abstracts Redis (working), SQLite (episodic), and MCP bridge (semantic).

Run: uvicorn memory_api:app --host 0.0.0.0 --port 8000
"""

import os
import sys
import json
from datetime import datetime
from typing import Optional, List, Dict, Any
from pathlib import Path

# Add genesis-system to path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
import redis

# Import Genesis memory components
from genesis_memory_cortex import MemoryCortex, Memory, MemoryTier
from mcp_memory_bridge import MCPMemoryBridge, MCPSyncer

# Load config
CONFIG_PATH = Path(r"E:\genesis-system\genesis_config.json")
with open(CONFIG_PATH) as f:
    CONFIG = json.load(f)

app = FastAPI(
    title="Genesis Memory API",
    description="Unified memory interface for all Genesis agents",
    version="1.0.0"
)

# Initialize components
cortex = MemoryCortex()
mcp_bridge = MCPMemoryBridge()

# Redis client for sync tracking
redis_client = redis.Redis(
    host=CONFIG["redis"]["host"],
    port=CONFIG["redis"]["port"],
    password=CONFIG["redis"]["password"],
    ssl=CONFIG["redis"].get("ssl", False),
    decode_responses=True
)

# --- Pydantic Models ---

class MemoryWriteRequest(BaseModel):
    content: str
    source: str = "unified_api"
    domain: str = "general"
    score: float = 0.5
    metadata: Optional[Dict[str, Any]] = None

class MemorySearchRequest(BaseModel):
    query: str
    limit: int = 10
    tier: Optional[str] = None  # working, episodic, semantic, or None for all

class SyncResponse(BaseModel):
    status: str
    pending_entities: int
    pending_relations: int
    last_sync: Optional[str]

# --- Endpoints ---

@app.get("/")
def root():
    return {"service": "Genesis Memory API", "status": "operational"}

@app.post("/memory/write")
def write_memory(req: MemoryWriteRequest):
    """
    Write memory to appropriate tier based on score.
    - score < 0.4: Working memory only (Redis, TTL 1hr)
    - score 0.4-0.7: Episodic memory (SQLite)
    - score > 0.7: Semantic memory (queued for MCP sync)
    """
    memory_id = cortex.store(
        content=req.content,
        source=req.source,
        domain=req.domain,
        score=req.score,
        metadata=req.metadata
    )
    
    # Determine tier based on score
    if req.score >= 0.7:
        # Also queue for MCP
        mcp_bridge.process_with_mcp(
            content=req.content,
            source=req.source,
            domain=req.domain,
            metadata=req.metadata
        )
        tier = "semantic"
    elif req.score >= 0.4:
        tier = "episodic"
    else:
        tier = "working"
    
    return {
        "memory_id": memory_id,
        "tier": tier,
        "timestamp": datetime.now().isoformat()
    }

@app.post("/memory/search")
def search_memory(req: MemorySearchRequest):
    """Search across all memory tiers."""
    results = cortex.search(
        query=req.query,
        limit=req.limit,
        tier=req.tier
    )
    
    return {
        "query": req.query,
        "count": len(results),
        "results": [
            {
                "id": m.id,
                "content": m.content[:200] + "..." if len(m.content) > 200 else m.content,
                "tier": m.tier.value if hasattr(m.tier, 'value') else str(m.tier),
                "score": m.score,
                "domain": m.domain,
                "timestamp": m.timestamp
            }
            for m in results
        ]
    }

@app.get("/memory/stats")
def get_stats():
    """Get unified statistics for all memory backends."""
    cortex_stats = cortex.stats
    pending = mcp_bridge.get_pending_mcp_operations()
    
    # Get last sync time from Redis
    last_sync = redis_client.get("genesis:memory_sync:last_run")
    
    return {
        "working_memory": cortex_stats.get("working", {}),
        "episodic_memory": cortex_stats.get("episodic", {}),
        "semantic_memory": cortex_stats.get("semantic", {}),
        "mcp_pending": {
            "entities": len(pending.get("entities", [])),
            "relations": len(pending.get("relations", []))
        },
        "last_sync": last_sync,
        "timestamp": datetime.now().isoformat()
    }

@app.post("/memory/sync")
def trigger_sync():
    """
    Trigger MCPSyncer to generate sync commands.
    Returns commands that can be executed in Claude Code context.
    """
    syncer = MCPSyncer(mcp_bridge)
    commands = syncer.generate_sync_commands()
    
    # Update sync timestamp
    redis_client.set(
        "genesis:memory_sync:last_run",
        datetime.now().isoformat()
    )
    
    pending = mcp_bridge.get_pending_mcp_operations()
    
    return {
        "status": "sync_commands_generated",
        "pending_entities": len(pending.get("entities", [])),
        "pending_relations": len(pending.get("relations", [])),
        "commands": commands,
        "last_sync": datetime.now().isoformat()
    }

@app.post("/memory/mark_synced")
def mark_synced():
    """Mark all pending operations as synced (called after Claude Code processes them)."""
    mcp_bridge.mark_synced()
    
    redis_client.set(
        "genesis:memory_sync:last_run",
        datetime.now().isoformat()
    )
    
    return {
        "status": "synced",
        "timestamp": datetime.now().isoformat()
    }

# --- Health Check ---

@app.get("/health")
def health_check():
    """Check connectivity to all backends."""
    status = {"api": "ok"}
    
    # Redis check
    try:
        redis_client.ping()
        status["redis"] = "ok"
    except Exception as e:
        status["redis"] = f"error: {str(e)}"
    
    # Cortex check
    try:
        cortex.stats
        status["cortex"] = "ok"
    except Exception as e:
        status["cortex"] = f"error: {str(e)}"
    
    return status

# --- CLI ---

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--test", action="store_true", help="Run basic tests")
    parser.add_argument("--port", type=int, default=8000, help="Port to run on")
    args = parser.parse_args()
    
    if args.test:
        print("[TEST] Memory API Self-Test")
        print(f"[OK] Root: {root()}")
        print(f"[OK] Health: {health_check()}")
        print(f"[OK] Stats: {get_stats()}")
        print("[DONE] All tests passed")
    else:
        import uvicorn
        uvicorn.run(app, host="0.0.0.0", port=args.port)
