"""SafeClaw FastAPI Gateway — HTTP/MCP entry point.

Invokes the LangGraph controller for every query.
Handles user confirmation flow for Grok fallback at the HTTP layer.
Binds to 127.0.0.1:8787 ONLY.
"""

import yaml
from fastapi import FastAPI, HTTPException

from graph import build_graph, GraphState
from retrieval.hybrid_search import HybridRetriever
from llm.client import LocalLLMClient, GrokClient
from schemas.api import QueryRequest, QueryResponse, SourceInfo, HealthResponse
from utils.logger import audit_log
from utils.sanitizer import check_input
from utils.errors import (
    RAGError, PromptInjectionError, IndexNotFoundError, LLMServiceError
)
from utils.health import check_all

# =============================================================================
# App Init
# =============================================================================

with open("config.yaml") as f:
    cfg = yaml.safe_load(f)

app = FastAPI(
    title="SafeClaw RAG Gateway",
    description="Offline-first, RAG-first, MCP-exposed stack",
    version="1.0.0"
)

# Initialize components
try:
    retriever = HybridRetriever()
except IndexNotFoundError as e:
    import sys
    print(f"FATAL: {e.message}", file=sys.stderr)
    print("Run: python -m retrieval.indexer", file=sys.stderr)
    retriever = None

local_llm = LocalLLMClient()

grok = None
if cfg["app"]["mode"] == "hybrid" and cfg["models"]["grok"].get("enabled", False):
    grok = GrokClient()

# Build graph (will be None if retriever failed)
compiled_graph = None
if retriever is not None:
    compiled_graph = build_graph(cfg, retriever, local_llm, grok)


# =============================================================================
# Endpoints
# =============================================================================

@app.post("/query", response_model=QueryResponse)
def query_endpoint(req: QueryRequest):
    """Main query endpoint. Invokes the LangGraph controller.

    Flow:
    1. Sanitize input (prompt filter)
    2. Build initial GraphState
    3. Invoke LangGraph (retrieve -> route -> llm/gate -> audit)
    4. If needs_confirm and no user_confirmed_online: return confirmation prompt
    5. Otherwise: return answer + sources
    """
    if compiled_graph is None:
        raise HTTPException(
            status_code=503,
            detail={"error": "Index not built. Run: python -m retrieval.indexer",
                    "code": "INDEX_NOT_FOUND"}
        )

    # 1. Sanitize input
    try:
        check_input(req.query)
    except PromptInjectionError as e:
        audit_log({"event": "prompt_injection_blocked", "query": req.query[:50]})
        raise HTTPException(
            status_code=400,
            detail={"error": e.message, "code": e.code, "details": e.details}
        )

    # 2. Build initial state
    initial_state: GraphState = {
        "query": req.query,
        "user_confirmed_online": req.user_confirmed_online
    }

    # 3. Invoke graph
    try:
        result = compiled_graph.invoke(initial_state)
    except Exception as e:
        audit_log({"event": "graph_error", "query": req.query[:50], "error": str(e)})
        raise HTTPException(status_code=500, detail={"error": str(e), "code": "GRAPH_ERROR"})

    # 4. Check if user gate is waiting for confirmation
    needs_confirm = result.get("needs_user_confirm", False)
    answer_model = result.get("answer_model", "")

    if needs_confirm and not answer_model:
        # Graph paused at user_gate — return confirmation prompt
        top_score = result.get("top_score", 0.0)
        threshold = cfg["retrieval"]["min_score"]
        return QueryResponse(
            answer="",
            sources=[],
            retrieval_mode=result.get("retrieval_mode", "hybrid"),
            hit_count=len(result.get("retrieved_docs", [])),
            model_used="",
            needs_confirm=True,
            confirm_message=(
                f"Vault miss (best score: {top_score:.3f} < {threshold}). "
                f"Send query to Grok online? Re-submit with user_confirmed_online=true/false."
            )
        )

    # 5. Return answer
    docs = result.get("answer_sources", [])
    sources = []
    for d in docs:
        if isinstance(d, dict):
            sources.append(SourceInfo(
                source=d.get("source", ""),
                score=d.get("score", 0.0),
                chunk_id=d.get("chunk_id", -1),
                stem_tags=d.get("stem_tags", [])
            ))

    return QueryResponse(
        answer=result.get("answer", "[No answer generated]"),
        sources=sources,
        retrieval_mode=result.get("retrieval_mode", "hybrid"),
        hit_count=len(result.get("retrieved_docs", [])),
        model_used=result.get("answer_model", "unknown"),
        needs_confirm=False,
        error=result.get("error")
    )


@app.get("/health", response_model=HealthResponse)
def health_check():
    """Health check for all dependencies."""
    statuses = check_all()
    all_healthy = all(s.healthy for s in statuses)
    return HealthResponse(
        status="ok" if all_healthy else "degraded",
        mode=cfg["app"]["mode"],
        dependencies=[
            {"name": s.name, "healthy": s.healthy,
             "latency_ms": s.latency_ms, "error": s.error}
            for s in statuses
        ]
    )


# =============================================================================
# Entry Point
# =============================================================================

if __name__ == "__main__":
    import uvicorn
    host = cfg["api"]["host"]
    port = cfg["api"]["port"]
    print(f"SafeClaw Gateway starting on {host}:{port}")
    print(f"Mode: {cfg['app']['mode']}")
    uvicorn.run(app, host=host, port=port)
