"""Web routes and data helpers for the unified ossctx UI."""

from __future__ import annotations

import json
from pathlib import Path

from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse

from codectx.db.repository import CodeRepository
from docsctx.db.repository import DocsRepository
from otelctx.db.repository import OtelRepository


CODE_DB = Path(".codecontext.db")
DOCS_DB = Path(".docscontext.db")
OTEL_DB = Path(".otelcontext.db")


def configure_databases(code_db: Path, docs_db: Path, otel_db: Path) -> None:
    """Set the database paths used by the UI."""
    global CODE_DB, DOCS_DB, OTEL_DB
    CODE_DB = code_db
    DOCS_DB = docs_db
    OTEL_DB = otel_db


def _templates(request: Request):
    return request.app.state.templates


def _code_repo() -> CodeRepository:
    repo = CodeRepository(CODE_DB)
    repo.create_schema()
    return repo


def _docs_repo() -> DocsRepository:
    repo = DocsRepository(DOCS_DB)
    repo.create_schema()
    return repo


def _otel_repo() -> OtelRepository:
    repo = OtelRepository(OTEL_DB)
    repo.create_schema()
    return repo


def _flatten_tree(tree: dict[str, object], prefix: str = "") -> tuple[list[str], list[str], list[int], list[str]]:
    labels: list[str] = []
    parents: list[str] = []
    values: list[int] = []
    ids: list[str] = []

    for name, value in tree.items():
        node_id = f"{prefix}/{name}" if prefix else name
        ids.append(node_id)
        labels.append(name)
        parents.append(prefix)
        if isinstance(value, dict) and "id" not in value:
            child_labels, child_parents, child_values, child_ids = _flatten_tree(value, node_id)
            labels.extend(child_labels)
            parents.extend(child_parents)
            values.extend(child_values)
            ids.extend(child_ids)
            values.append(max(1, sum(child_values) if child_values else 1))
        else:
            values.append(max(1, int(value.get("lines_of_code", 1))))
    return labels, parents, values, ids


def _make_treemap(tree: dict[str, object]) -> dict[str, object]:
    labels: list[str] = []
    parents: list[str] = []
    values: list[int] = []
    ids: list[str] = []

    def walk(node: dict[str, object], parent: str = "") -> int:
        total = 0
        for name, value in node.items():
            node_id = f"{parent}/{name}" if parent else name
            if isinstance(value, dict) and "id" not in value:
                child_total = walk(value, node_id)
                labels.append(name)
                parents.append(parent)
                values.append(max(1, child_total))
                ids.append(node_id)
                total += max(1, child_total)
            else:
                loc = int(value.get("lines_of_code", 1))
                labels.append(name)
                parents.append(parent)
                values.append(max(1, loc))
                ids.append(node_id)
                total += max(1, loc)
        return total

    walk(tree)
    return {
        "data": [{"type": "treemap", "ids": ids, "labels": labels, "parents": parents, "values": values, "branchvalues": "total", "textinfo": "label+value"}],
        "layout": {"margin": {"l": 0, "r": 0, "t": 0, "b": 0}},
    }


def _network_figure(nodes: list[dict[str, object]], edges: list[tuple[str, str]], title: str) -> dict[str, object]:
    positions: dict[str, tuple[float, float]] = {}
    if nodes:
        for index, node in enumerate(nodes):
            angle = 6.283185307179586 * index / max(len(nodes), 1)
            positions[str(node["id"])] = (0.5 + 0.35 * __import__("math").cos(angle), 0.5 + 0.35 * __import__("math").sin(angle))

    edge_x: list[float] = []
    edge_y: list[float] = []
    for source, target in edges:
        if source not in positions or target not in positions:
            continue
        sx, sy = positions[source]
        tx, ty = positions[target]
        edge_x.extend([sx, tx, None])
        edge_y.extend([sy, ty, None])

    node_x = [positions[str(node["id"])] [0] for node in nodes] if nodes else []
    node_y = [positions[str(node["id"])] [1] for node in nodes] if nodes else []
    return {
        "data": [
            {"type": "scatter", "x": edge_x, "y": edge_y, "mode": "lines", "line": {"color": "#94a3b8", "width": 1}, "hoverinfo": "none"},
            {"type": "scatter", "x": node_x, "y": node_y, "mode": "markers+text", "text": [node["label"] for node in nodes], "textposition": "top center", "marker": {"size": 16, "color": [node.get("color", "#4f46e5") for node in nodes], "line": {"color": "#fff", "width": 1}}, "hovertemplate": "%{text}<extra></extra>"},
        ],
        "layout": {"title": title, "margin": {"l": 0, "r": 0, "t": 40, "b": 0}, "xaxis": {"visible": False}, "yaxis": {"visible": False}, "paper_bgcolor": "rgba(0,0,0,0)", "plot_bgcolor": "rgba(0,0,0,0)"},
    }


def _code_graph() -> dict[str, object]:
    repo = _code_repo()
    files = repo.list_files(limit=200)
    file_ids = [item["id"] for item in files]
    dependencies = repo.get_dependencies_by_file_ids(file_ids)
    nodes = []
    for file_row in files:
        nodes.append({"id": str(file_row["id"]), "label": Path(str(file_row["path"])).name, "color": "#2563eb"})
    edges = [(str(dep["file_id"]), str(file_row_id)) for dep in dependencies for file_row_id in [next((row["id"] for row in files if row["path"] == dep["resolved"]), None)] if file_row_id]
    return _network_figure(nodes, edges, "Code dependencies")


def _docs_graph(query: str = "") -> dict[str, object]:
    repo = _docs_repo()
    entity_name = query.strip()
    if not entity_name:
        entities = repo.list_all_entities(limit=1)
        entity_name = entities[0]["name"] if entities else ""
    if not entity_name:
        return {"data": [], "layout": {"title": "No entities indexed yet"}}
    graph = repo.get_graph_neighborhood(entity_name, depth=2, max_nodes=40)
    if graph is None:
        return {"data": [], "layout": {"title": "No matching entity found"}}
    nodes = [{"id": node["name"], "label": node["name"], "color": "#16a34a"} for node in graph["nodes"]]
    edges = [(edge["source"], edge["target"]) for edge in graph["edges"]]
    return _network_figure(nodes, edges, f"Docs graph: {graph['center']}")


def _otel_service_graph() -> dict[str, object]:
    repo = _otel_repo()
    services = repo.list_services()
    nodes = [{"id": service.service_name, "label": service.service_name, "color": "#d97706" if service.error_count else "#0f766e"} for service in services]
    edges: list[tuple[str, str]] = []
    traces = repo.list_traces(limit=50)
    for trace in traces:
        full = repo.get_trace(trace["trace_id"])
        if not full:
            continue
        span_by_id = {span["span_id"]: span for span in full.get("spans", [])}
        for span in full.get("spans", []):
            parent_id = span.get("parent_span_id")
            parent = span_by_id.get(parent_id)
            if parent and parent.get("service_name") != span.get("service_name"):
                edges.append((parent.get("service_name"), span.get("service_name")))
    edges = list(dict.fromkeys(edges))
    return _network_figure(nodes, edges, "Service topology")


def _trace_waterfall(trace: dict[str, object]) -> dict[str, object]:
    spans = trace.get("spans", [])
    if not spans:
        return {"data": [], "layout": {"title": "Trace spans"}}
    labels = [span["name"] for span in spans]
    durations = [float(span.get("duration_ms", 0.0)) for span in spans]
    return {
        "data": [{"type": "bar", "x": durations, "y": labels, "orientation": "h", "marker": {"color": "#2563eb"}}],
        "layout": {"title": f"Trace waterfall: {trace['trace_id']}", "margin": {"l": 180, "r": 20, "t": 40, "b": 40}},
    }


def _render(request: Request, template: str, **context: object) -> HTMLResponse:
    return _templates(request).TemplateResponse(request, template, context)


def build_router() -> APIRouter:
    router = APIRouter()

    @router.get("/", response_class=HTMLResponse)
    async def dashboard(request: Request) -> HTMLResponse:
        code = _code_repo().get_stats().to_dict()
        docs = _docs_repo().get_stats().to_dict()
        otel = _otel_repo().get_stats().to_dict()
        return _render(request, "dashboard.html", page_title="Dashboard", code=code, docs=docs, otel=otel)

    @router.get("/code", response_class=HTMLResponse)
    async def code_view(request: Request) -> HTMLResponse:
        repo = _code_repo()
        stats = repo.get_stats().to_dict()
        files = repo.list_files(limit=20)
        entities = repo.search_entities("")[:20]
        return _render(request, "code.html", page_title="Code", stats=stats, files=files, entities=entities, plot=_make_treemap(repo.get_directory_tree()))

    @router.get("/docs", response_class=HTMLResponse)
    async def docs_view(request: Request, q: str = "") -> HTMLResponse:
        repo = _docs_repo()
        stats = repo.get_stats().to_dict()
        documents = repo.list_documents(limit=20)
        results = [item.to_dict() for item in repo.search(q, top_k=10)] if q else []
        communities = [item.to_dict() for item in repo.list_communities()]
        return _render(request, "docs.html", page_title="Docs", stats=stats, documents=documents, results=results, communities=communities, query=q, plot=_docs_graph(q))

    @router.get("/otel", response_class=HTMLResponse)
    async def otel_view(request: Request) -> HTMLResponse:
        repo = _otel_repo(); stats = repo.get_stats().to_dict(); traces = repo.list_traces(limit=20); logs = repo.list_logs(limit=15); services = [service.to_dict() for service in repo.list_services()]
        traces_plot = {"data": [{"type": "bar", "x": [trace["trace_id"] for trace in traces], "y": [trace["duration_ms"] for trace in traces], "marker": {"color": "#7c3aed"}}], "layout": {"title": "Recent traces", "margin": {"l": 40, "r": 20, "t": 40, "b": 80}}}
        return _render(request, "otel.html", page_title="Otel", stats=stats, traces=traces, logs=logs, services=services, plot=traces_plot, topology=_otel_service_graph())

    @router.get("/otel/traces/{trace_id}", response_class=HTMLResponse)
    async def trace_detail(request: Request, trace_id: str) -> HTMLResponse:
        trace = _otel_repo().get_trace(trace_id)
        if trace is None:
            return HTMLResponse("<p class='empty-state'>Trace not found.</p>")
        return _render(request, "partials/trace_detail.html", page_title="Trace detail", trace=trace, plot=_trace_waterfall(trace))

    @router.get("/mcp", response_class=HTMLResponse)
    async def mcp_view(request: Request) -> HTMLResponse:
        tool_groups = {
            "Code": ["index", "stats", "find_entities", "dependency_graph", "call_graph", "get_docs", "list_files", "get_file_outline", "find_usages", "search_entities", "get_entity_code", "get_file_imports", "get_graph_context", "get_file_context"],
            "Docs": ["index", "stats", "search", "communities", "document", "document_versions", "upload", "finalize", "graph_neighborhood", "entities"],
            "Otel": ["ingest", "stats", "traces", "logs", "metrics", "services", "graph", "archive", "dlq", "sampler"],
        }
        return _render(request, "mcp.html", page_title="MCP", tool_groups=tool_groups)

    return router
