"""MCP server for otelctx — 12 tools for observability queries."""

from __future__ import annotations

import logging
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from otelctx.config import OtelSettings
from otelctx.db.repository import OtelRepository


def build_mcp_server(settings: OtelSettings, host: str = "127.0.0.1", port: int = 8082):
    """Create an MCP server exposing otelctx tools."""
    from mcp.server.fastmcp import FastMCP

    repository = OtelRepository(settings.db_path)
    repository.create_schema()

    logger = logging.getLogger("otelctx.mcp")

    server = FastMCP(
        name="otelctx",
        instructions="Query the local OTEL telemetry store for traces, logs, metrics, and service health.",
        host=host,
        port=port,
    )

    # ------------------------------------------------------------------
    # Service graph and health
    # ------------------------------------------------------------------

    @server.tool(description="Return the in-process service dependency graph reconstructed from span parent/child relationships.")
    def get_system_graph() -> dict[str, Any]:
        from otelctx.graph import ServiceGraph
        graph = ServiceGraph()
        graph.rebuild(repository)
        return graph.get_graph()

    @server.tool(description="Return health details for a single service, including upstream and downstream edges.")
    def get_service_health(service_name: str) -> dict[str, Any]:
        from otelctx.graph import ServiceGraph
        graph = ServiceGraph()
        graph.rebuild(repository)
        result = graph.get_service_health(service_name)
        if result is None:
            raise ValueError(f"Service '{service_name}' not found in span data")
        return result

    # ------------------------------------------------------------------
    # Logs
    # ------------------------------------------------------------------

    @server.tool(description="Return recent log entries. Optionally filter by service_name, severity (DEBUG/INFO/WARN/ERROR), or keyword in body.")
    def search_logs(
        service_name: str = "",
        severity: str = "",
        keyword: str = "",
        limit: int = 50,
        offset: int = 0,
    ) -> list[dict[str, Any]]:
        logs = repository.list_logs(limit=limit * 4, offset=offset)
        if service_name:
            logs = [l for l in logs if l.get("service_name") == service_name]
        if severity:
            logs = [l for l in logs if (l.get("severity_text") or "").upper() == severity.upper()]
        if keyword:
            kw = keyword.lower()
            logs = [l for l in logs if kw in (l.get("body") or "").lower()]
        return logs[:limit]

    @server.tool(description="Return the N most-recently-ingested log entries across all services.")
    def tail_logs(limit: int = 20) -> list[dict[str, Any]]:
        return repository.list_logs(limit=limit, offset=0)

    @server.tool(description="Return log entries at ERROR, CRITICAL, or FATAL severity level as an alert list.")
    def get_alerts(service_name: str = "", limit: int = 50) -> list[dict[str, Any]]:
        logs = repository.list_logs(limit=1000, offset=0)
        error_levels = {"error", "critical", "fatal", "err", "crit"}
        alerts = [
            l for l in logs
            if (l.get("severity_text") or "").lower() in error_levels
        ]
        if service_name:
            alerts = [l for l in alerts if l.get("service_name") == service_name]
        return alerts[:limit]

    @server.tool(description="Find log entries similar to a query string using in-memory TF-IDF cosine similarity.")
    def find_similar_logs(
        query: str,
        top_k: int = 10,
        service_name: str = "",
        severity: str = "",
    ) -> list[dict[str, Any]]:
        from otelctx.logindex import IndexedLog, LogVectorIndex

        index = LogVectorIndex(capacity=10_000)
        raw_logs = repository.list_logs(limit=5000, offset=0)
        for l in raw_logs:
            index.add(IndexedLog(
                log_id=l["id"],
                body=l.get("body") or "",
                service_name=l.get("service_name") or "",
                severity=l.get("severity_text") or "",
                timestamp=l.get("timestamp") or "",
            ))
        return index.search(
            query,
            top_k=top_k,
            service_name=service_name or None,
            severity=severity or None,
        )

    # ------------------------------------------------------------------
    # Traces
    # ------------------------------------------------------------------

    @server.tool(description="Return a paginated list of trace summaries, newest first.")
    def list_traces(limit: int = 20, offset: int = 0) -> list[dict[str, Any]]:
        return repository.list_traces(limit=limit, offset=offset)

    @server.tool(description="Return the full trace detail with all spans for the given trace_id.")
    def get_trace(trace_id: str) -> dict[str, Any]:
        result = repository.get_trace(trace_id)
        if result is None:
            raise ValueError(f"Trace '{trace_id}' not found")
        return result

    @server.tool(description="Search traces by optional service_name, status_code (OK/ERROR/UNSET), or root_span_name substring.")
    def search_traces(
        service_name: str = "",
        status_code: str = "",
        root_span_name: str = "",
        limit: int = 20,
    ) -> list[dict[str, Any]]:
        traces = repository.list_traces(limit=limit * 4, offset=0)
        if service_name:
            traces = [t for t in traces if t.get("root_service_name") == service_name]
        if status_code:
            traces = [t for t in traces if (t.get("status_code") or "").upper() == status_code.upper()]
        if root_span_name:
            sub = root_span_name.lower()
            traces = [t for t in traces if sub in (t.get("root_span_name") or "").lower()]
        return traces[:limit]

    # ------------------------------------------------------------------
    # Metrics
    # ------------------------------------------------------------------

    @server.tool(description="Return recent metric data points. Optionally filter by service_name or metric name substring.")
    def get_metrics(
        service_name: str = "",
        metric_name: str = "",
        limit: int = 50,
        offset: int = 0,
    ) -> list[dict[str, Any]]:
        metrics = repository.list_metrics(limit=limit * 4, offset=offset)
        if service_name:
            metrics = [m for m in metrics if m.get("service_name") == service_name]
        if metric_name:
            sub = metric_name.lower()
            metrics = [m for m in metrics if sub in (m.get("name") or "").lower()]
        return metrics[:limit]

    # ------------------------------------------------------------------
    # Stats and infrastructure
    # ------------------------------------------------------------------

    @server.tool(description="Return aggregate counts: traces, spans, logs, metrics, services, error_spans.")
    def get_dashboard_stats() -> dict[str, Any]:
        stats = repository.get_stats()
        services = repository.list_services()
        return {
            **stats.to_dict(),
            "services": [s.to_dict() for s in services],
        }

    @server.tool(description="Return storage information including database file size and record counts.")
    def get_storage_status() -> dict[str, Any]:
        db_path_obj = Path(settings.db_path)
        size_bytes = db_path_obj.stat().st_size if db_path_obj.exists() else 0
        stats = repository.get_stats()
        return {
            "db_path": str(db_path_obj),
            "db_size_bytes": size_bytes,
            "db_size_mb": round(size_bytes / (1024 * 1024), 3),
            **stats.to_dict(),
        }

    @server.tool(description="Search the cold archive for telemetry by keyword. Returns matching compressed JSONL records.")
    def search_cold_archive(
        query: str = "",
        signal: str = "traces",
        days_back: int = 30,
        limit: int = 50,
    ) -> list[dict[str, Any]]:
        from otelctx.archive import ArchiveTier
        archive_dir = Path(settings.db_path).parent / "archive"
        if not archive_dir.exists():
            return []
        tier = ArchiveTier(archive_dir)
        return tier.search_cold(query or None, signal=signal, days_back=days_back, limit=limit)

    # ------------------------------------------------------------------
    # Infrastructure metrics (placeholder)
    # ------------------------------------------------------------------

    @server.tool()
    def check_infra_metrics(
        service_name: str,
        metric_type: str = "all",
        source: str = "prometheus",
        api_url: str | None = None,
        time_range_minutes: int = 15,
    ) -> dict:
        """Check infrastructure metrics for a service (placeholder — logs request only).

        When implemented, will query Prometheus/CloudWatch/custom APIs.
        metric_type: "cpu", "memory", "disk", "network", or "all".
        source: "prometheus", "cloudwatch", or "api".
        """
        request = {
            "service": service_name,
            "metric_type": metric_type,
            "source": source,
            "api_url": api_url or settings.infra_metrics_url,
            "time_range_minutes": time_range_minutes,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "status": "placeholder",
        }
        logger.info("INFRA_METRICS_REQUEST: %s", request)
        return {
            "status": "placeholder",
            "message": f"Infrastructure metrics check for '{service_name}' logged (not yet connected)",
            "request": request,
            "expected_response_schema": {
                "cpu_percent": "float",
                "memory_mb": "float",
                "disk_usage_percent": "float",
                "network_io_bytes": "float",
                "pod_count": "int",
                "pod_status": "dict[str, int]",
                "uptime_seconds": "float",
            },
        }

    @server.tool()
    def list_infra_sources() -> list[dict]:
        """List configured infrastructure metrics sources."""
        return [
            {"name": "prometheus", "description": "Query Prometheus API", "status": "placeholder", "url": settings.infra_metrics_url or "(not configured)"},
            {"name": "cloudwatch", "description": "Query AWS CloudWatch", "status": "placeholder"},
            {"name": "custom_api", "description": "Query custom metrics API", "status": "placeholder"},
        ]

    return server


def build_mcp_server_entry(
    binary: str,
    transport: str,
    addr: str,
    db_path: str | None,
) -> dict[str, object]:
    """Build a VS Code MCP server config entry for otelctx."""
    if transport == "stdio":
        args = ["otel", "mcp", "--transport", "stdio"]
        if db_path:
            args.extend(["--db", db_path])
        return {
            "type": "stdio",
            "command": binary,
            "args": args,
        }
    if transport == "streamable-http":
        return {"type": "http", "url": f"http://{addr}/mcp"}
    if transport == "sse":
        return {"type": "http", "url": f"http://{addr}/sse"}
    raise ValueError(f"Unsupported MCP transport: {transport}")
