"""FastAPI server — HTTP API for the Neo Cortex memory system."""

import logging
from contextlib import asynccontextmanager
from typing import Optional

from fastapi import FastAPI, HTTPException

from neo_cortex import config
from neo_cortex.classifier import Classifier
from neo_cortex.cortex import CortexService
from neo_cortex.embedder import JinaEmbedder
from neo_cortex.llm import create_client
from neo_cortex.conversation_log import ConversationLog
from neo_cortex.memory_index import MemoryIndex
from neo_cortex.models import ConversationAppendRequest, IngestRequest
from neo_cortex.store import MemoryStore

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)
logger = logging.getLogger(__name__)

# Global instances (initialized in lifespan)
_cortex: Optional[CortexService] = None
_embedder: Optional[JinaEmbedder] = None
_classifier: Optional[Classifier] = None
_index: Optional[MemoryIndex] = None
_conv_log: Optional[ConversationLog] = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    global _cortex, _embedder, _classifier, _index, _conv_log

    # Load API keys
    jina_key = config.load_api_key("jina")

    # Initialize components
    store = MemoryStore(config.CORTEX_DB_PATH, config.COLLECTION_NAME)
    _embedder = JinaEmbedder(jina_key)
    _classifier = Classifier(create_client())
    _index = MemoryIndex(config.MEMORY_INDEX_DB_PATH)
    _cortex = CortexService(store, _embedder, _classifier, index=_index)
    _conv_log = ConversationLog(config.CONVERSATION_DB_PATH)

    count = store.count()
    conv_count = _conv_log.count()
    logger.info(
        "Cortex started: %d memories, %d conversation entries",
        count, conv_count,
    )

    yield

    # Cleanup
    if _embedder:
        await _embedder.close()
    if _classifier:
        await _classifier.close()
    logger.info("Cortex stopped")


app = FastAPI(title="Neo Cortex", version="3.0.0", lifespan=lifespan)


def _get_cortex() -> CortexService:
    if _cortex is None:
        raise HTTPException(status_code=503, detail="Cortex not initialized")
    return _cortex


@app.get("/health")
def health():
    cortex = _get_cortex()
    return {"status": "ok", "stats": cortex.stats().model_dump()}


@app.post("/api/ingest")
async def ingest(request: IngestRequest):
    cortex = _get_cortex()
    memory_id = await cortex.ingest(request)
    if memory_id:
        return {"status": "ingested", "id": memory_id}
    return {"status": "skipped", "reason": "noise"}


@app.get("/api/query")
async def query(q: str, n: int = 5, smart: bool = True):
    cortex = _get_cortex()
    result = await cortex.recall(q, n=n, smart=smart)
    return result.model_dump()


@app.post("/api/dream")
async def dream():
    cortex = _get_cortex()
    result = await cortex.dream()
    return result.model_dump()


@app.get("/api/stats")
def stats():
    cortex = _get_cortex()
    return cortex.stats().model_dump()


@app.get("/api/timeline")
async def timeline(n: int = 10, project: Optional[str] = None, smart: bool = False):
    cortex = _get_cortex()
    result = await cortex.timeline(n=n, project=project, smart=smart)
    return result.model_dump()


def _get_index() -> MemoryIndex:
    if _index is None:
        raise HTTPException(status_code=503, detail="Memory index not initialized")
    return _index


@app.get("/api/search")
def search(q: str = "", project: Optional[str] = None,
           activity: Optional[str] = None, n: int = 10):
    """Search memories — returns compact index (low token cost)."""
    index = _get_index()
    results = index.search_fts(q, project=project, activity=activity, n=n)
    return {"count": len(results), "results": [r.model_dump() for r in results]}


@app.get("/api/memory/{memory_id}")
def get_memory(memory_id: str):
    """Get full memory detail by ID."""
    index = _get_index()
    record = index.get_by_id(memory_id)
    if not record:
        raise HTTPException(404, "Memory not found")
    return record.model_dump()


@app.get("/api/memories")
def get_memories(ids: str):
    """Get multiple memories by comma-separated IDs."""
    index = _get_index()
    id_list = [i.strip() for i in ids.split(",") if i.strip()]
    records = index.get_by_ids(id_list)
    return {"count": len(records), "memories": [r.model_dump() for r in records]}


def _get_conv_log() -> ConversationLog:
    if _conv_log is None:
        raise HTTPException(status_code=503, detail="Conversation log not initialized")
    return _conv_log


@app.post("/api/conversation/append")
def conversation_append(request: ConversationAppendRequest):
    log = _get_conv_log()
    row_id = log.append(request)
    return {"status": "ok", "id": row_id}


@app.get("/api/conversation/sessions")
def conversation_sessions():
    log = _get_conv_log()
    return {"sessions": log.get_sessions()}


@app.get("/api/conversation/session/{session_id}")
def conversation_session(session_id: str):
    log = _get_conv_log()
    entries = log.get_session(session_id)
    return {"session_id": session_id, "count": len(entries),
            "entries": [e.model_dump() for e in entries]}


@app.get("/api/conversation/recent")
def conversation_recent(n: int = 50):
    log = _get_conv_log()
    entries = log.get_recent(n)
    return {"count": len(entries),
            "entries": [e.model_dump() for e in entries]}


def main():
    import argparse
    import uvicorn

    parser = argparse.ArgumentParser(description="Neo Cortex memory server")
    parser.add_argument("--port", type=int, default=config.CORTEX_PORT,
                        help=f"Port to listen on (default: {config.CORTEX_PORT})")
    parser.add_argument("--host", default=config.CORTEX_HOST,
                        help=f"Host to bind to (default: {config.CORTEX_HOST})")
    parser.add_argument("--data-dir", default=None,
                        help="Data directory (sets cortex_db, memory_index, conversation_log paths)")
    args = parser.parse_args()

    if args.data_dir:
        from pathlib import Path
        data = Path(args.data_dir)
        data.mkdir(parents=True, exist_ok=True)
        config.CORTEX_DB_PATH = str(data / "cortex_db")
        config.MEMORY_INDEX_DB_PATH = str(data / "memory_index.db")
        config.CONVERSATION_DB_PATH = str(data / "conversation_log.db")

    uvicorn.run(
        "neo_cortex.server:app",
        host=args.host,
        port=args.port,
        log_level="info",
    )


if __name__ == "__main__":
    main()
