"""
Helix API Server — Unified FastAPI application.

All ingestion and retrieval flows through **LightRAG** as the sole orchestrator.
Graphiti Core is accessed **only** through the GraphitiStorage adapter
(LightRAG's graph-storage plugin).  Specialized endpoints (temporal,
multi-hop, hallucination validation) use the Graphiti instance that
GraphitiStorage exposes via ``helix.graphiti``.

Usage::

    helix serve                          # 0.0.0.0:9621
    helix serve --port 8000 --reload     # dev mode
    uvicorn helix.api.server:app         # direct uvicorn
"""

from __future__ import annotations

import os
import logging
from contextlib import asynccontextmanager
from typing import Any

from dotenv import load_dotenv

load_dotenv(dotenv_path=".env", override=False)

logger = logging.getLogger("helix.api")

# ────────────────────────────────────────────────────────────────────
#  Global Helix instance
# ────────────────────────────────────────────────────────────────────

_helix_instance = None


def _get_helix():
    if _helix_instance is None:
        raise RuntimeError("Helix not initialised — server still starting up.")
    return _helix_instance


# ────────────────────────────────────────────────────────────────────
#  App factory
# ────────────────────────────────────────────────────────────────────

def create_app() -> "FastAPI":
    from fastapi import FastAPI
    from fastapi.middleware.cors import CORSMiddleware

    @asynccontextmanager
    async def lifespan(app: FastAPI):
        global _helix_instance
        from helix.core import Helix, HelixConfig

        logger.info("Helix API starting — initialising pipeline ...")
        _helix_instance = Helix(config=HelixConfig())
        await _helix_instance.initialize()
        logger.info("Helix pipeline ready (LightRAG sole orchestrator).")
        yield
        logger.info("Helix API shutting down ...")
        if _helix_instance:
            await _helix_instance.close()
        _helix_instance = None
        logger.info("Helix shutdown complete.")

    app = FastAPI(
        title="Helix API",
        description=(
            "Unified REST API for the Helix Temporal GraphRAG pipeline. "
            "LightRAG is the sole orchestrator; Graphiti is integrated "
            "as a storage plugin for entity resolution, dedup, and temporal KG."
        ),
        version=_get_version(),
        lifespan=lifespan,
    )

    origins = os.getenv("HELIX_CORS_ORIGINS", "*").split(",")
    app.add_middleware(
        CORSMiddleware,
        allow_origins=origins,
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    _register_routes(app)
    return app


def _get_version() -> str:
    try:
        from helix import __version__
        return __version__
    except Exception:
        return "0.0.0"


# ────────────────────────────────────────────────────────────────────
#  Routes
# ────────────────────────────────────────────────────────────────────

def _register_routes(app: "FastAPI"):
    from fastapi import HTTPException
    from helix.api.models import (
        QueryRequest,
        QueryResponse,
        TemporalQueryRequest,
        MultiHopQueryRequest,
        MultiHopQueryResponse,
        InsertRequest,
        InsertWithMetadataRequest,
        InsertBatchRequest,
        InsertResponse,
        ValidateRequest,
        ValidateResponse,
        HealthResponse,
        KnowledgeGraphRequest,
        KnowledgeGraphResponse,
    )

    # ── Health ───────────────────────────────────────────────────────

    @app.get("/health", response_model=HealthResponse, tags=["System"])
    async def health_check():
        helix = _get_helix()
        return HealthResponse(
            status="ok",
            version=_get_version(),
            components={
                "lightrag": "connected" if helix.lightrag else "disconnected",
                "graphiti": "connected" if helix.graphiti else "disconnected",
            },
        )

    # ── Standard Query (single pipeline) ─────────────────────────────

    @app.post("/query", response_model=QueryResponse, tags=["Query"])
    async def query(req: QueryRequest):
        """Standard RAG query — goes through LightRAG's full pipeline."""
        helix = _get_helix()
        try:
            answer = await helix.query(
                query_text=req.query,
                mode=req.mode,
                top_k=req.top_k,
            )
            return QueryResponse(
                answer=answer,
                query=req.query,
                mode=req.mode,
            )
        except Exception as exc:
            logger.exception("Query failed")
            raise HTTPException(status_code=500, detail=str(exc))

    # ── Temporal Query ───────────────────────────────────────────────

    @app.post("/query/temporal", response_model=QueryResponse, tags=["Query"])
    async def query_temporal(req: TemporalQueryRequest):
        """Time-aware query.

        First searches Graphiti's temporal KG (via the adapter), then
        feeds the temporal context into LightRAG's standard pipeline.
        """
        helix = _get_helix()
        try:
            from helix.temporal import TemporalQueryHandler
            from helix.utils.temporal_utils import TemporalParams

            handler = TemporalQueryHandler(graphiti=helix.graphiti)

            params = TemporalParams(
                has_temporal_intent=True,
                valid_at=req.valid_at,
                valid_from=req.valid_from,
                valid_to=req.valid_to,
                temporal_keywords=[],
            )

            graph_results = await handler.search_temporal_graph(
                query=req.query,
                valid_at=req.valid_at,
                num_results=req.top_k,
            )
            filtered = await handler.apply_temporal_filter(graph_results, params)

            # Run the main pipeline as well
            answer = await helix.query(
                query_text=req.query,
                mode=req.mode,
                top_k=req.top_k,
            )

            return QueryResponse(
                answer=answer,
                query=req.query,
                mode=req.mode,
                temporal_context=filtered,
                valid_at=req.valid_at.isoformat() if req.valid_at else "current",
            )
        except Exception as exc:
            logger.exception("Temporal query failed")
            raise HTTPException(status_code=500, detail=str(exc))

    # ── Multi-Hop Query ──────────────────────────────────────────────

    @app.post("/query/multihop", response_model=MultiHopQueryResponse, tags=["Query"])
    async def query_multihop(req: MultiHopQueryRequest):
        """Multi-hop reasoning via BFS path exploration."""
        helix = _get_helix()
        try:
            from helix.multihop import MultiHopRetriever

            retriever = MultiHopRetriever(
                graphiti=helix.graphiti,
                max_hops=req.max_hops,
                max_paths=req.max_paths,
            )

            paths = await retriever.find_paths(
                query=req.query,
                seed_entities=req.seed_entities,
            )

            path_dicts = [
                {
                    "nodes": p.nodes,
                    "edges": p.edges,
                    "score": p.total_score,
                    "hops": p.hop_count,
                }
                for p in paths
            ]

            # Main pipeline answer
            answer = await helix.query(
                query_text=req.query,
                mode=req.mode,
                top_k=req.top_k,
            )

            used_seeds = list({n for p in paths for n in p.nodes[:1]}) if paths else []

            return MultiHopQueryResponse(
                answer=answer,
                query=req.query,
                mode=req.mode,
                paths=path_dicts,
                seed_entities=used_seeds,
            )
        except Exception as exc:
            logger.exception("Multi-hop query failed")
            raise HTTPException(status_code=500, detail=str(exc))

    # ── Insert (single pipeline) ─────────────────────────────────────

    @app.post("/insert", response_model=InsertResponse, tags=["Ingest"])
    async def insert(req: InsertRequest):
        """Insert a document — single pipeline through LightRAG.

        LightRAG chunks, extracts entities, writes to GraphitiStorage,
        and embeds into VectorDB.  GraphitiStorage's index_done_callback
        fires Graphiti's add_episode for dedup & temporal tracking.
        """
        helix = _get_helix()
        try:
            track_id = await helix.insert(
                document=req.document,
                doc_id=req.doc_id,
            )
            return InsertResponse(
                doc_id=req.doc_id or "auto",
                status="success",
                message=f"Document inserted via LightRAG pipeline (track: {track_id}).",
            )
        except Exception as exc:
            logger.exception("Insert failed")
            raise HTTPException(status_code=500, detail=str(exc))

    @app.post("/insert/with-metadata", response_model=InsertResponse, tags=["Ingest"])
    async def insert_with_metadata(req: InsertWithMetadataRequest):
        """Insert with temporal metadata — same single pipeline."""
        helix = _get_helix()
        try:
            track_id = await helix.insert(
                document=req.document,
                doc_id=req.doc_id,
            )
            return InsertResponse(
                doc_id=req.doc_id or "auto",
                status="success",
                message=f"Document inserted with metadata (track: {track_id}).",
            )
        except Exception as exc:
            logger.exception("Insert with metadata failed")
            raise HTTPException(status_code=500, detail=str(exc))

    @app.post("/insert/batch", response_model=list[InsertResponse], tags=["Ingest"])
    async def insert_batch(req: InsertBatchRequest):
        """Batch insert — single pipeline."""
        helix = _get_helix()
        responses: list[InsertResponse] = []
        for doc_req in req.documents:
            try:
                track_id = await helix.insert(
                    document=doc_req.document,
                    doc_id=doc_req.doc_id,
                )
                responses.append(InsertResponse(
                    doc_id=doc_req.doc_id or "auto",
                    status="success",
                    message=f"Inserted (track: {track_id}).",
                ))
            except Exception as exc:
                responses.append(InsertResponse(
                    doc_id=doc_req.doc_id or "unknown",
                    status="error",
                    message=str(exc),
                ))
        return responses

    # ── Validation (Hallucination Detection) ─────────────────────────

    @app.post("/validate", response_model=ValidateResponse, tags=["Validation"])
    async def validate(req: ValidateRequest):
        """Validate a response against the knowledge graph (CFI)."""
        helix = _get_helix()
        try:
            from helix.hallucination import HallucinationDetector

            detector = HallucinationDetector(
                graphiti=helix.graphiti,
                grounding_threshold=req.grounding_threshold,
            )

            result = await detector.verify_response(
                response=req.response,
                query=req.query,
                context=req.context,
            )

            return ValidateResponse(
                is_grounded=result.is_grounded,
                confidence_score=result.confidence_score,
                entity_coverage=result.entity_coverage,
                relation_coverage=result.relation_coverage,
                temporal_consistency=result.temporal_consistency,
                ungrounded_claims=list(result.ungrounded_claims),
                verification_details=result.verification_details,
            )
        except Exception as exc:
            logger.exception("Validation failed")
            raise HTTPException(status_code=500, detail=str(exc))

    # ── Knowledge Graph Exploration ──────────────────────────────────

    @app.post("/graph/explore", response_model=KnowledgeGraphResponse, tags=["Graph"])
    async def graph_explore(req: KnowledgeGraphRequest):
        """Explore the knowledge graph around an entity."""
        helix = _get_helix()
        try:
            result = await helix.get_knowledge_graph(
                entity_name=req.entity_name,
                max_depth=req.max_depth,
                max_nodes=req.max_nodes,
            )
            return KnowledgeGraphResponse(
                nodes=result.get("nodes", []),
                edges=result.get("edges", []),
                is_truncated=result.get("is_truncated", False),
            )
        except Exception as exc:
            logger.exception("Graph exploration failed")
            raise HTTPException(status_code=500, detail=str(exc))


# ────────────────────────────────────────────────────────────────────
#  Module-level app instance
# ────────────────────────────────────────────────────────────────────
app = create_app()
