"""Metric management tools for Metabase v0.49+ card-based metrics."""

from __future__ import annotations

from typing import Any

from metaverse.app import mcp
from metaverse.client import as_dict, as_list
from metaverse.client import get as api_get
from metaverse.client import post as api_post
from metaverse.client import put as api_put
from metaverse.config import settings


def _build_aggregation(aggregation_type: str, field_id: int | None) -> list[Any]:
    """Build an MBQL aggregation clause.

    For "count" no field is needed. All other types require a field_id.
    Returns a list of one aggregation expression, e.g. [["sum", ["field", 42, null]]].
    """
    if aggregation_type == "count":
        return [["count"]]
    if field_id is None:
        raise ValueError(f"field_id is required for aggregation_type '{aggregation_type}'")
    return [[aggregation_type, ["field", field_id, None]]]


def _format_result(data: dict[str, Any], limit: int) -> dict[str, Any]:
    """Normalise a Metabase dataset response into a clean table dict."""
    inner: dict[str, Any] = data.get("data", data)  # type: ignore[assignment]
    cols: list[str] = [c["name"] for c in inner.get("cols", [])]
    rows: list[Any] = inner.get("rows", [])
    return {
        "columns": cols,
        "rows": rows,
        "row_count": len(rows),
        "truncated": len(rows) >= limit,
    }


@mcp.tool()
async def list_metrics() -> list[dict[str, Any]]:
    """List all Metrics defined in Metabase (Metabase v0.49+).

    Metrics are reusable MBQL aggregations (e.g. "Total Revenue", "Active Users")
    tied to a specific table.

    Returns [{id, name, description, table_id, database_id, archived}].
    """
    data = await api_get("/card")
    return [
        {
            "id": m["id"],
            "name": m["name"],
            "description": m.get("description") or "",
            "table_id": m.get("table_id"),
            "database_id": m.get("database_id"),
            "archived": m.get("archived", False),
        }
        for m in as_list(data)
        if m.get("type") == "metric"
    ]


@mcp.tool()
async def get_metric(metric_id: int) -> dict[str, Any]:
    """Get the full details of a Metric, including its MBQL definition.

    Args:
        metric_id: The metric ID from list_metrics.

    Returns {id, name, description, table_id, database_id, definition, archived}.
    The definition dict contains the raw MBQL query (source-table + aggregation).
    """
    data = as_dict(await api_get(f"/card/{metric_id}"))
    dq = data.get("dataset_query") or {}
    definition = dq.get("query") or dq.get("stages", [{}])[0] or {}
    return {
        "id": data["id"],
        "name": data["name"],
        "description": data.get("description") or "",
        "table_id": data.get("table_id"),
        "database_id": data.get("database_id"),
        "definition": definition,
        "archived": data.get("archived", False),
    }


@mcp.tool()
async def create_metric(
    name: str,
    database_id: int,
    table_id: int,
    aggregation_type: str,
    field_id: int | None = None,
    description: str = "",
) -> dict[str, Any]:
    """Create a new Metric in Metabase (Metabase v0.49+).

    Always call describe_table first to find the correct field_id.

    Args:
        name: A clear, descriptive name (e.g. "Total Revenue", "Active Users Count").
        database_id: The database ID from list_databases.
        table_id: The table this metric is based on (from list_tables).
        aggregation_type: One of "count", "sum", "avg", "distinct", "min", "max".
                          "count" does not require a field_id.
        field_id: The field to aggregate on (from describe_table). Required for all
                  aggregation types except "count".
        description: Optional description of what the metric measures.

    Returns {id, name, url}.
    """
    aggregation = _build_aggregation(aggregation_type, field_id)
    payload: dict[str, Any] = {
        "name": name,
        "type": "metric",
        "description": description or None,
        "dataset_query": {
            "database": database_id,
            "type": "query",
            "query": {
                "source-table": table_id,
                "aggregation": aggregation,
            },
        },
        "display": "scalar",
        "visualization_settings": {},
    }
    data = as_dict(await api_post("/card", json=payload))
    return {
        "id": data["id"],
        "name": data["name"],
        "url": f"{settings.url}/metric/{data['id']}",
    }


@mcp.tool()
async def archive_metric(metric_id: int) -> dict[str, Any]:
    """Archive (soft-delete) a Metric in Metabase.

    The metric is hidden but not permanently removed. It can be restored from
    the Metabase admin UI if needed.

    Args:
        metric_id: The metric ID from list_metrics.

    Returns {archived, metric_id}.
    """
    current = as_dict(await api_get(f"/card/{metric_id}"))
    payload = {**current, "archived": True}
    await api_put(f"/card/{metric_id}", json=payload)
    return {"archived": True, "metric_id": metric_id}


@mcp.tool()
async def execute_metric(metric_id: int, limit: int = 100) -> dict[str, Any]:
    """Run a Metric and return its aggregated result.

    Args:
        metric_id: The metric ID from list_metrics.
        limit: Maximum rows to return (default 100).

    Returns {columns, rows, row_count, truncated}.
    For scalar metrics (single aggregated value) this will be a single row.
    """
    limit = min(limit, 2000)
    raw = await api_post(f"/card/{metric_id}/query/json", json={})
    if isinstance(raw, list):
        columns = list(raw[0].keys()) if raw else []
        rows = [[row.get(c) for c in columns] for row in raw[:limit]]
        return {
            "columns": columns,
            "rows": rows,
            "row_count": len(rows),
            "truncated": len(raw) > limit,
        }
    return _format_result(as_dict(raw), limit)
