"""FastAPI application factory for the unified ossctx UI."""

from __future__ import annotations

import contextlib
import logging
from pathlib import Path

from fastapi import FastAPI
from pydantic import BaseModel

from common.server import create_app, mount_spa
from common.version import __version__

from codectx.cli import build_app as build_code_app
from codectx.config import CodeSettings
from codectx.db.repository import CodeRepository
from docsctx.cli import build_app as build_docs_app
from docsctx.config import DocsSettings
from docsctx.db.repository import DocsRepository
from otelctx.cli import build_app as build_otel_app
from otelctx.config import OtelSettings
from otelctx.db.repository import OtelRepository
from otelctx.api.routes import ingest_payload

logger = logging.getLogger(__name__)

# Collect MCP servers for lifespan management
_mcp_servers: list = []


def _dist_dir() -> Path:
    return Path(__file__).resolve().parent / "static" / "dist"


class ContextQuery(BaseModel):
    query: str
    budget: int = 8000
    subsystems: list[str] | None = None


def build_app(
    code_db: str | Path = ".codecontext.db",
    docs_db: str | Path = ".docscontext.db",
    otel_db: str | Path = ".otelcontext.db",
) -> FastAPI:
    """Create the unified UI application."""

    @contextlib.asynccontextmanager
    async def lifespan(app_instance: FastAPI):
        """Manage MCP session manager lifespans."""
        async with contextlib.AsyncExitStack() as stack:
            for mcp_server in _mcp_servers:
                try:
                    await stack.enter_async_context(mcp_server.session_manager.run())
                except Exception as exc:
                    logger.warning("Failed to start MCP session manager: %s", exc)
            yield

    app = create_app("ossctx-ui", __version__)
    app.router.lifespan_context = lifespan

    app.mount("/api/code", build_code_app(CodeSettings(db_path=Path(code_db))))
    app.mount("/api/docs", build_docs_app(DocsSettings(db_path=Path(docs_db))))

    otel_settings = OtelSettings(db_path=Path(otel_db))
    otel_app = build_otel_app(otel_settings)
    app.mount("/api/otel", otel_app)

    # Mount MCP servers using streamable HTTP transport (SSE is deprecated)
    from codectx.cli import build_mcp_server as build_code_mcp
    from docsctx.mcp import build_mcp_server as build_docs_mcp
    from otelctx.mcp import build_mcp_server as build_otel_mcp

    code_mcp = build_code_mcp(CodeSettings(db_path=Path(code_db)), host="0.0.0.0", port=8081)
    code_mcp.settings.streamable_http_path = "/"
    app.mount("/mcp/code", code_mcp.streamable_http_app())
    _mcp_servers.append(code_mcp)

    otel_mcp = build_otel_mcp(otel_settings, host="0.0.0.0", port=8082)
    otel_mcp.settings.streamable_http_path = "/"
    app.mount("/mcp/otel", otel_mcp.streamable_http_app())
    _mcp_servers.append(otel_mcp)

    docs_settings = DocsSettings(db_path=Path(docs_db))
    docs_repo = DocsRepository(Path(docs_db))
    docs_mcp = build_docs_mcp(docs_repo, docs_settings, host="0.0.0.0", port=8083)
    docs_mcp.settings.streamable_http_path = "/"
    app.mount("/mcp/docs", docs_mcp.streamable_http_app())
    _mcp_servers.append(docs_mcp)

    # -- Unified context & agent MCP server -----------------------------------
    _mount_unified_mcp(app, code_db, docs_db, otel_db)

    # -- Unified context assembly REST endpoint -------------------------------
    code_repo = CodeRepository(Path(code_db))
    code_repo.create_schema()
    otel_repo = OtelRepository(Path(otel_db))
    otel_repo.create_schema()

    assembler = _create_assembler(code_repo, docs_repo, otel_repo)
    app.state.assembler = assembler

    @app.post("/api/context/assemble")
    async def assemble_context(body: ContextQuery) -> dict:
        """Assemble cross-system context for an LLM query."""
        ctx = assembler.assemble(
            body.query, context_budget=body.budget, subsystems=body.subsystems,
        )
        return ctx.to_dict()

    @app.get("/api/context/route")
    async def route_query(query: str) -> dict:
        """Determine which subsystems are relevant to a query."""
        from common.context import route_query as _route
        return {"query": query, "subsystems": _route(query)}

    # Expose standard OTLP HTTP receiver endpoints at the root for compatibility
    @app.post("/v1/traces")
    async def ingest_otlp_traces(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(otel_repo, payload)

    @app.post("/v1/logs")
    async def ingest_otlp_logs(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(otel_repo, payload)

    @app.post("/v1/metrics")
    async def ingest_otlp_metrics(payload: dict[str, object]) -> dict[str, int]:
        return ingest_payload(otel_repo, payload)

    # Mount React SPA LAST so catch-all doesn't shadow API/MCP routes
    mount_spa(app, _dist_dir())

    return app


def _create_assembler(code_repo, docs_repo, otel_repo):
    """Create a ContextAssembler with optional vector stores and graph."""
    from common.context import ContextAssembler

    # Try to initialize vector stores and graph (best-effort)
    code_vs = None
    docs_vs = None
    graph_store = None

    try:
        from codectx.vectorstore import CodeVectorStore
        code_vs = CodeVectorStore()
    except Exception:
        pass

    try:
        from docsctx.vectorstore import DocsVectorStore
        docs_vs = DocsVectorStore()
    except Exception:
        pass

    try:
        from common.graphdb import GraphStore
        graph_store = GraphStore()
        graph_store.build_from_code_repo(code_repo)
        graph_store.build_from_docs_repo(docs_repo)
    except Exception:
        pass

    return ContextAssembler(
        code_repo=code_repo,
        docs_repo=docs_repo,
        otel_repo=otel_repo,
        code_vectorstore=code_vs,
        docs_vectorstore=docs_vs,
        graph_store=graph_store,
    )


def _mount_unified_mcp(app: FastAPI, code_db, docs_db, otel_db) -> None:
    """Mount a unified MCP server with cross-system tools."""
    try:
        from mcp.server.fastmcp import FastMCP

        mcp = FastMCP(
            name="ossctx-unified",
            instructions="Unified agent interface for code, docs, and telemetry context.",
            stateless_http=True,
            json_response=True,
        )

        code_repo = CodeRepository(Path(code_db))
        code_repo.create_schema()
        docs_repo = DocsRepository(Path(docs_db))
        otel_repo = OtelRepository(Path(otel_db))
        otel_repo.create_schema()

        assembler = _create_assembler(code_repo, docs_repo, otel_repo)

        from common.agent import AgentInterface
        agent = AgentInterface(
            assembler=assembler,
            code_repo=code_repo,
            docs_repo=docs_repo,
            otel_repo=otel_repo,
        )

        @mcp.tool(description="Smart cross-context search: assembles code + docs + telemetry context for any query.")
        def query(question: str, budget: int = 8000) -> dict:
            ctx = agent.query(question, budget=budget)
            return ctx.to_dict()

        @mcp.tool(description="Get a full overview of a component: code entities, docs, and recent telemetry.")
        def component_overview(name: str) -> dict:
            from dataclasses import asdict
            return asdict(agent.get_component_overview(name))

        @mcp.tool(description="Investigate an incident: trace -> related code -> docs -> similar past incidents.")
        def incident_context(trace_id: str) -> dict:
            from dataclasses import asdict
            return asdict(agent.get_incident_context(trace_id))

        @mcp.tool(description="Show what changed between code versions.")
        def diff_versions(old_version: str) -> dict:
            return agent.diff_versions(old_version)

        @mcp.tool(description="Determine which subsystems are relevant to a query.")
        def route_query(question: str) -> dict:
            from common.context import route_query as _route
            return {"query": question, "subsystems": _route(question)}

        # Register action tools
        from common.actions.mcp import register_action_tools
        register_action_tools(mcp)

        mcp.settings.streamable_http_path = "/"
        app.mount("/mcp/unified", mcp.streamable_http_app())
        _mcp_servers.append(mcp)
    except Exception as exc:
        logger.warning("Failed to mount unified MCP server: %s", exc)
