from __future__ import annotations

import hashlib
import json
import os
import re
from datetime import datetime, timezone
from threading import Lock
from typing import Any

from src.langfuse_control import (
    execute_control_message,
    LangfuseControlService,
    parse_control_message,
)
from src.tools.helpers import _env_int

try:
    from langchain_core.tools import tool as _lc_tool
except Exception:
    def _lc_tool(*_args, **_kwargs):
        def _decorator(fn):
            return fn

        return _decorator

_OPENAPI_TOOL_REGISTRY_LOCK = Lock()
_OPENAPI_TOOL_REGISTRY_CACHE: dict[str, Any] = {}


def run_langfuse_control_operation(
    operation: str,
    params: dict[str, Any] | None = None,
    settings: dict[str, Any] | None = None,
) -> dict[str, Any]:
    service = LangfuseControlService(settings=settings)
    return service.execute({"operation": operation, "params": params or {}})


def run_langfuse_control_message(message: str, settings: dict[str, Any] | None = None) -> dict[str, Any]:
    return execute_control_message(message, settings=settings)


def parse_langfuse_control_message(message: str) -> dict[str, Any]:
    return parse_control_message(message)


def is_langfuse_openapi_mutating(
    *,
    operation_id: str | None = None,
    method: str | None = None,
    path: str | None = None,
) -> bool:
    service = LangfuseControlService()
    return service.openapi_is_mutating(operation_id=operation_id, method=method, path=path)


def _env_optional_bool(name: str) -> bool | None:
    raw = os.getenv(name, "").strip().lower()
    if not raw:
        return None
    if raw in {"1", "true", "yes", "y", "on"}:
        return True
    if raw in {"0", "false", "no", "n", "off"}:
        return False
    return None


def _sanitize_openapi_tool_name(raw: str) -> str:
    normalized = re.sub(r"[^a-zA-Z0-9_]+", "_", str(raw or "").strip().lower()).strip("_")
    if not normalized:
        normalized = "operation"
    if normalized[0].isdigit():
        normalized = f"op_{normalized}"
    return normalized[:72]


def _openapi_operation_signature(operations: list[dict[str, Any]]) -> str:
    return hashlib.sha256(
        json.dumps(operations, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8")
    ).hexdigest()


def _openapi_operation_description(operation: dict[str, Any]) -> str:
    operation_id = str(operation.get("operation_id") or "").strip()
    method = str(operation.get("method") or "").strip().upper() or "GET"
    path = str(operation.get("path") or "").strip() or "/"
    mutating = bool(operation.get("mutating", False))
    summary = str(operation.get("summary") or "").strip()
    prefix = f"Execute Langfuse OpenAPI operation `{operation_id or f'{method}_{path}'}` ({method} {path})."
    guardrail = (
        " Mutating operation: requires AGENT_LANGFUSE_CONTROL_ALLOW_MUTATION=true."
        if mutating
        else " Read-only operation."
    )
    hint = " Use path_params/query/body inputs as needed."
    if summary:
        return f"{prefix} {summary}{guardrail}{hint}"
    return f"{prefix}{guardrail}{hint}"


def _make_langfuse_openapi_operation_tool(
    operation: dict[str, Any],
    *,
    tool_name: str,
):
    operation_id = str(operation.get("operation_id") or "").strip() or None
    method = str(operation.get("method") or "").strip().upper() or None
    path = str(operation.get("path") or "").strip() or None
    description = _openapi_operation_description(operation)

    def _operation_tool(
        path_params: dict[str, Any] | None = None,
        query: dict[str, Any] | None = None,
        body: dict[str, Any] | None = None,
        refresh_spec: bool = False,
    ) -> str:
        result = run_langfuse_control_operation(
            "openapi.execute",
            {
                "operation_id": operation_id,
                "method": method,
                "path": path,
                "path_params": path_params or {},
                "query": query or {},
                "body": body,
                "refresh_spec": refresh_spec,
            },
        )
        return json.dumps(result, ensure_ascii=False, indent=2)

    _operation_tool.__name__ = tool_name
    _operation_tool.__doc__ = description
    return _lc_tool(tool_name)(_operation_tool)


def clear_langfuse_openapi_tool_registry_cache() -> None:
    with _OPENAPI_TOOL_REGISTRY_LOCK:
        _OPENAPI_TOOL_REGISTRY_CACHE.clear()


def build_langfuse_openapi_operation_tools(
    *,
    refresh: bool = False,
    include_mutating: bool | None = None,
    max_tools: int | None = None,
) -> list[Any]:
    if include_mutating is None:
        include_mutating = _env_optional_bool("AGENT_LANGFUSE_OPENAPI_DYNAMIC_INCLUDE_MUTATING")
    if include_mutating is None:
        include_mutating = True

    if max_tools is None:
        max_tools = _env_int("AGENT_LANGFUSE_OPENAPI_DYNAMIC_TOOL_MAX", default=300, minimum=1, maximum=2000)

    result = run_langfuse_control_operation("openapi.list_operations", {"refresh": refresh})
    payload = result.get("result") if isinstance(result, dict) else {}
    operations_raw = payload.get("operations") if isinstance(payload, dict) else []
    if not isinstance(operations_raw, list):
        operations_raw = []

    operations: list[dict[str, Any]] = []
    for item in operations_raw:
        if not isinstance(item, dict):
            continue
        mutating = bool(item.get("mutating", False))
        if mutating and not include_mutating:
            continue
        operation_id = str(item.get("operation_id") or "").strip()
        method = str(item.get("method") or "").strip().upper()
        path = str(item.get("path") or "").strip()
        if not method or not path:
            continue
        operations.append(
            {
                "operation_id": operation_id,
                "method": method,
                "path": path,
                "mutating": mutating,
                "summary": str(item.get("summary") or "").strip(),
            }
        )

    operations = operations[: max(1, int(max_tools))]
    signature = _openapi_operation_signature(operations)

    with _OPENAPI_TOOL_REGISTRY_LOCK:
        cached = _OPENAPI_TOOL_REGISTRY_CACHE.get("entry")
        if (
            not refresh
            and isinstance(cached, dict)
            and cached.get("signature") == signature
            and isinstance(cached.get("tools"), list)
        ):
            return list(cached["tools"])

    generated_tools: list[Any] = []
    used_names: set[str] = set()
    for idx, operation in enumerate(operations, start=1):
        operation_id = str(operation.get("operation_id") or "").strip()
        method = str(operation.get("method") or "").strip().lower()
        path = str(operation.get("path") or "").strip()
        suffix_seed = operation_id or f"{method}_{path}"
        base_name = f"langfuse_api_{_sanitize_openapi_tool_name(suffix_seed)}"
        tool_name = base_name
        duplicate_seq = 2
        while tool_name in used_names:
            tool_name = f"{base_name}_{duplicate_seq}"
            duplicate_seq += 1
        used_names.add(tool_name)
        generated_tools.append(_make_langfuse_openapi_operation_tool(operation, tool_name=tool_name))
        if idx >= max_tools:
            break

    with _OPENAPI_TOOL_REGISTRY_LOCK:
        _OPENAPI_TOOL_REGISTRY_CACHE["entry"] = {
            "signature": signature,
            "operations": operations,
            "tools": list(generated_tools),
            "generated_at": datetime.now(timezone.utc).isoformat(),
        }

    return generated_tools


def get_langfuse_openapi_tool_registry(
    *,
    refresh: bool = False,
    include_mutating: bool | None = None,
    max_tools: int | None = None,
) -> dict[str, Any]:
    tools = build_langfuse_openapi_operation_tools(
        refresh=refresh,
        include_mutating=include_mutating,
        max_tools=max_tools,
    )
    with _OPENAPI_TOOL_REGISTRY_LOCK:
        cached = _OPENAPI_TOOL_REGISTRY_CACHE.get("entry") or {}
    operations = cached.get("operations") if isinstance(cached, dict) else []
    if not isinstance(operations, list):
        operations = []
    return {
        "tool_count": len(tools),
        "operation_count": len(operations),
        "tools": [
            {
                "name": str(getattr(tool, "name", "") or getattr(tool, "__name__", "")),
                "description": str(getattr(tool, "description", "") or getattr(tool, "__doc__", "")),
            }
            for tool in tools
        ],
        "operations": operations,
        "include_mutating": include_mutating if include_mutating is not None else True,
    }


@_lc_tool("langfuse_control_execute")
def langfuse_control_execute_tool(operation: str, params: dict[str, Any] | None = None) -> str:
    """Execute a Langfuse control operation. Use operation + params to call control-plane features."""
    result = run_langfuse_control_operation(operation=operation, params=params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_control_message")
def langfuse_control_message_tool(message: str) -> str:
    """Execute a free-form Langfuse control command message (kv or JSON)."""
    result = run_langfuse_control_message(message)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_traces_list")
def langfuse_traces_list_tool(
    limit: int = 20,
    page: int | None = None,
    name: str | None = None,
    user_id: str | None = None,
    session_id: str | None = None,
    environment: str | None = None,
    tags: list[str] | None = None,
    from_timestamp: str | None = None,
    to_timestamp: str | None = None,
) -> str:
    """List Langfuse traces with common filters."""
    params: dict[str, Any] = {"limit": max(1, int(limit))}
    if page is not None:
        params["page"] = max(1, int(page))
    if name:
        params["name"] = name
    if user_id:
        params["user_id"] = user_id
    if session_id:
        params["session_id"] = session_id
    if environment:
        params["environment"] = environment
    if isinstance(tags, list) and tags:
        params["tags"] = [str(item).strip() for item in tags if str(item).strip()]
    if from_timestamp:
        params["from_timestamp"] = from_timestamp
    if to_timestamp:
        params["to_timestamp"] = to_timestamp
    result = run_langfuse_control_operation("traces.list", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_trace_get")
def langfuse_trace_get_tool(trace_id: str) -> str:
    """Get a single trace by trace_id."""
    result = run_langfuse_control_operation("trace.get", {"trace_id": trace_id})
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_observations_list")
def langfuse_observations_list_tool(
    trace_id: str | None = None,
    name: str | None = None,
    type: str | None = None,
    limit: int = 50,
    page: int | None = None,
) -> str:
    """List observations (optionally scoped by trace_id)."""
    params: dict[str, Any] = {"limit": max(1, int(limit))}
    if trace_id:
        params["trace_id"] = trace_id
    if name:
        params["name"] = name
    if type:
        params["type"] = type
    if page is not None:
        params["page"] = max(1, int(page))
    result = run_langfuse_control_operation("observations.list", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_prompt_get")
def langfuse_prompt_get_tool(name: str, label: str | None = None, version: int | None = None) -> str:
    """Fetch a prompt by name (and optional label/version)."""
    params: dict[str, Any] = {"name": name}
    if label:
        params["label"] = label
    if version is not None:
        params["version"] = int(version)
    result = run_langfuse_control_operation("prompt.get", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_prompt_list")
def langfuse_prompt_list_tool(page: int | None = None, limit: int | None = None) -> str:
    """List prompt definitions."""
    params: dict[str, Any] = {}
    if page is not None:
        params["page"] = max(1, int(page))
    if limit is not None:
        params["limit"] = max(1, int(limit))
    result = run_langfuse_control_operation("prompt.list", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_prompt_upsert")
def langfuse_prompt_upsert_tool(
    name: str,
    prompt: Any,
    prompt_type: str = "text",
    labels: list[str] | None = None,
    tags: list[str] | None = None,
    commit_message: str | None = None,
    config: dict[str, Any] | None = None,
) -> str:
    """Create/update a prompt (mutation guard applies)."""
    params: dict[str, Any] = {
        "name": name,
        "prompt": prompt,
        "type": prompt_type,
    }
    if isinstance(labels, list):
        params["labels"] = [str(item).strip() for item in labels if str(item).strip()]
    if isinstance(tags, list):
        params["tags"] = [str(item).strip() for item in tags if str(item).strip()]
    if commit_message:
        params["commit_message"] = commit_message
    if isinstance(config, dict):
        params["config"] = config
    result = run_langfuse_control_operation("prompt.upsert", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_prompt_delete")
def langfuse_prompt_delete_tool(name: str, version: int | None = None, label: str | None = None) -> str:
    """Delete a prompt version/label mapping (mutation guard applies)."""
    params: dict[str, Any] = {"name": name}
    if version is not None:
        params["version"] = int(version)
    if label:
        params["label"] = label
    result = run_langfuse_control_operation("prompt.delete", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_prompt_promote_label")
def langfuse_prompt_promote_label_tool(name: str, label: str, version: int | None = None) -> str:
    """Promote/move a prompt label to a target version (mutation guard applies)."""
    params: dict[str, Any] = {"name": name, "label": label}
    if version is not None:
        params["version"] = int(version)
    result = run_langfuse_control_operation("prompt.promote_label", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_score_create")
def langfuse_score_create_tool(
    name: str,
    value: float,
    trace_id: str | None = None,
    observation_id: str | None = None,
    comment: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> str:
    """Create evaluation score for trace/observation (mutation guard applies)."""
    params: dict[str, Any] = {"name": name, "value": float(value)}
    if trace_id:
        params["trace_id"] = trace_id
    if observation_id:
        params["observation_id"] = observation_id
    if comment:
        params["comment"] = comment
    if isinstance(metadata, dict):
        params["metadata"] = metadata
    result = run_langfuse_control_operation("score.create", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_annotation_create")
def langfuse_annotation_create_tool(
    label: str,
    trace_id: str | None = None,
    observation_id: str | None = None,
    comment: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> str:
    """Create evaluation annotation for trace/observation (mutation guard applies)."""
    params: dict[str, Any] = {"label": label}
    if trace_id:
        params["trace_id"] = trace_id
    if observation_id:
        params["observation_id"] = observation_id
    if comment:
        params["comment"] = comment
    if isinstance(metadata, dict):
        params["metadata"] = metadata
    result = run_langfuse_control_operation("annotation.create", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_datasets_list")
def langfuse_datasets_list_tool(page: int | None = None, limit: int | None = None, name: str | None = None) -> str:
    """List datasets."""
    params: dict[str, Any] = {}
    if page is not None:
        params["page"] = max(1, int(page))
    if limit is not None:
        params["limit"] = max(1, int(limit))
    if name:
        params["name"] = name
    result = run_langfuse_control_operation("datasets.list", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_datasets_create")
def langfuse_datasets_create_tool(
    name: str,
    description: str | None = None,
    metadata: dict[str, Any] | None = None,
) -> str:
    """Create dataset (mutation guard applies)."""
    params: dict[str, Any] = {"name": name}
    if description is not None:
        params["description"] = description
    if isinstance(metadata, dict):
        params["metadata"] = metadata
    result = run_langfuse_control_operation("datasets.create", params)
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_dataset_items_upsert")
def langfuse_dataset_items_upsert_tool(dataset_id: str, items: list[dict[str, Any]]) -> str:
    """Upsert dataset items (mutation guard applies)."""
    result = run_langfuse_control_operation(
        "dataset.items.upsert",
        {
            "dataset_id": dataset_id,
            "items": items if isinstance(items, list) else [],
        },
    )
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_datasets_import_from_traces")
def langfuse_datasets_import_from_traces_tool(
    dataset_id: str,
    trace_ids: list[str],
    item_template: dict[str, Any] | None = None,
) -> str:
    """Import trace references into a dataset as items (mutation guard applies)."""
    result = run_langfuse_control_operation(
        "datasets.import_from_traces",
        {
            "dataset_id": dataset_id,
            "trace_ids": [str(item).strip() for item in trace_ids if str(item).strip()],
            "item_template": item_template if isinstance(item_template, dict) else {},
        },
    )
    return json.dumps(result, ensure_ascii=False, indent=2)


def get_langfuse_builtin_tool_registry(include_mutating: bool = True) -> dict[str, Any]:
    tool_specs: list[dict[str, Any]] = [
        {"name": "langfuse_traces_list", "category": "traces", "mutating": False, "fn": langfuse_traces_list_tool},
        {"name": "langfuse_trace_get", "category": "traces", "mutating": False, "fn": langfuse_trace_get_tool},
        {"name": "langfuse_observations_list", "category": "traces", "mutating": False, "fn": langfuse_observations_list_tool},
        {"name": "langfuse_prompt_get", "category": "prompts", "mutating": False, "fn": langfuse_prompt_get_tool},
        {"name": "langfuse_prompt_list", "category": "prompts", "mutating": False, "fn": langfuse_prompt_list_tool},
        {"name": "langfuse_prompt_upsert", "category": "prompts", "mutating": True, "fn": langfuse_prompt_upsert_tool},
        {"name": "langfuse_prompt_delete", "category": "prompts", "mutating": True, "fn": langfuse_prompt_delete_tool},
        {
            "name": "langfuse_prompt_promote_label",
            "category": "prompts",
            "mutating": True,
            "fn": langfuse_prompt_promote_label_tool,
        },
        {"name": "langfuse_score_create", "category": "evaluation", "mutating": True, "fn": langfuse_score_create_tool},
        {
            "name": "langfuse_annotation_create",
            "category": "evaluation",
            "mutating": True,
            "fn": langfuse_annotation_create_tool,
        },
        {"name": "langfuse_datasets_list", "category": "datasets", "mutating": False, "fn": langfuse_datasets_list_tool},
        {"name": "langfuse_datasets_create", "category": "datasets", "mutating": True, "fn": langfuse_datasets_create_tool},
        {
            "name": "langfuse_dataset_items_upsert",
            "category": "datasets",
            "mutating": True,
            "fn": langfuse_dataset_items_upsert_tool,
        },
        {
            "name": "langfuse_datasets_import_from_traces",
            "category": "datasets",
            "mutating": True,
            "fn": langfuse_datasets_import_from_traces_tool,
        },
        {"name": "user_reports_list", "category": "reports", "mutating": False, "fn": user_reports_list_tool},
        {"name": "user_report_detail", "category": "reports", "mutating": False, "fn": user_report_detail_tool},
        {"name": "report_session_turns", "category": "reports", "mutating": False, "fn": report_session_turns_tool},
    ]
    if not include_mutating:
        tool_specs = [spec for spec in tool_specs if not spec["mutating"]]
    return {
        "count": len(tool_specs),
        "tools": [
            {
                "name": spec["name"],
                "category": spec["category"],
                "mutating": bool(spec["mutating"]),
                "description": str(getattr(spec["fn"], "description", "") or getattr(spec["fn"], "__doc__", "")),
            }
            for spec in tool_specs
        ],
    }


@_lc_tool("langfuse_openapi_spec")
def langfuse_openapi_spec_tool(full: bool = False, refresh: bool = False) -> str:
    """Get Langfuse OpenAPI spec metadata (or full spec)."""
    result = run_langfuse_control_operation("openapi.spec", {"full": full, "refresh": refresh})
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_openapi_list_operations")
def langfuse_openapi_list_operations_tool(refresh: bool = False) -> str:
    """List all Langfuse OpenAPI operations with method/path metadata."""
    result = run_langfuse_control_operation("openapi.list_operations", {"refresh": refresh})
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("user_reports_list")
def user_reports_list_tool(
    owner_id: str,
    target_user_id: str | None = None,
    period_type: str | None = None,
    limit: int = 20,
    offset: int = 0,
) -> str:
    """List user activity reports scoped to owner_id. Filter by target_user_id and/or period_type (daily/weekly/monthly). owner_id is required for access control."""
    from src.user_store import list_user_reports, count_user_reports

    items = list_user_reports(
        owner_id=owner_id,
        target_user_id=target_user_id,
        period_type=period_type,
        limit=max(1, min(int(limit), 100)),
        offset=max(0, int(offset)),
    )
    total = count_user_reports(owner_id=owner_id, target_user_id=target_user_id, period_type=period_type)
    compact = []
    for r in items:
        compact.append({
            "id": r["id"],
            "target_user_id": r.get("target_user_id"),
            "period_type": r.get("period_type"),
            "period_start": r.get("period_start"),
            "period_end": r.get("period_end"),
            "trace_count": r.get("trace_count", 0),
            "total_cost": r.get("total_cost", 0),
            "created_at": r.get("created_at"),
        })
    return json.dumps({"items": compact, "total": total}, ensure_ascii=False, indent=2)


@_lc_tool("user_report_detail")
def user_report_detail_tool(report_id: int, owner_id: str = "") -> str:
    """Get full detail of a user activity report by ID. If owner_id is provided, access is restricted to reports owned by that user."""
    from src.user_store import get_user_report

    report = get_user_report(report_id)
    if not report:
        return json.dumps({"error": f"Report {report_id} not found"}, ensure_ascii=False)
    if owner_id and report.get("owner_id") != owner_id:
        return json.dumps({"error": "Access denied"}, ensure_ascii=False)
    return json.dumps(report, ensure_ascii=False, indent=2)


@_lc_tool("report_session_turns")
def report_session_turns_tool(
    session_id: str,
    user_id: str | None = None,
    from_timestamp: str | None = None,
    to_timestamp: str | None = None,
    limit: int = 50,
    page: int = 1,
) -> str:
    """Fetch conversation turns (input/output) for a specific session. Returns traces sorted by timestamp with their input and output content. Use this after user_report_detail to drill into session details."""
    safe_limit = max(1, min(int(limit), 100))
    safe_page = max(1, int(page))
    params: dict[str, Any] = {
        "session_id": session_id,
        "limit": safe_limit,
        "page": safe_page,
    }
    if user_id:
        params["user_id"] = user_id
    if from_timestamp:
        params["from_timestamp"] = from_timestamp
    if to_timestamp:
        params["to_timestamp"] = to_timestamp

    result = run_langfuse_control_operation("traces.list", params)

    # Navigate: result > result > data (SDK Traces object) > .data (list)
    inner_result = result.get("result", result)
    raw_data = inner_result.get("data", None) if isinstance(inner_result, dict) else None

    # Handle SDK Pydantic objects (Traces) with .data attribute
    if raw_data is not None and hasattr(raw_data, "data"):
        trace_objects = raw_data.data or []
    elif isinstance(raw_data, dict):
        trace_objects = raw_data.get("data", [])
    elif isinstance(raw_data, list):
        trace_objects = raw_data
    else:
        trace_objects = []

    def _trace_to_dict(t):
        if isinstance(t, dict):
            return t
        if hasattr(t, "model_dump"):
            return t.model_dump()
        if hasattr(t, "dict"):
            return t.dict()
        return vars(t)

    turns = []
    for t in trace_objects:
        d = _trace_to_dict(t)
        turns.append({
            "trace_id": d.get("id"),
            "name": d.get("name"),
            "timestamp": str(d.get("timestamp", "")),
            "input": d.get("input"),
            "output": d.get("output"),
            "latency_ms": d.get("latency"),
            "total_cost": d.get("totalCost") or d.get("total_cost"),
            "tags": d.get("tags", []),
        })

    turns.sort(key=lambda x: x.get("timestamp") or "")

    return json.dumps(
        {"session_id": session_id, "turn_count": len(turns), "turns": turns, "page": safe_page},
        ensure_ascii=False,
        indent=2,
    )


@_lc_tool("langfuse_openapi_build_tools")
def langfuse_openapi_build_tools_tool(
    refresh: bool = False,
    include_mutating: bool | None = None,
    max_tools: int | None = None,
) -> str:
    """Build dynamic Langfuse OpenAPI tools and return tool registry metadata."""
    result = get_langfuse_openapi_tool_registry(
        refresh=refresh,
        include_mutating=include_mutating,
        max_tools=max_tools,
    )
    return json.dumps(result, ensure_ascii=False, indent=2)


@_lc_tool("langfuse_openapi_execute")
def langfuse_openapi_execute_tool(
    operation_id: str | None = None,
    method: str | None = None,
    path: str | None = None,
    path_params: dict[str, Any] | None = None,
    query: dict[str, Any] | None = None,
    body: dict[str, Any] | None = None,
    refresh_spec: bool = False,
) -> str:
    """Execute any Langfuse OpenAPI operation via operation_id or method+path."""
    result = run_langfuse_control_operation(
        "openapi.execute",
        {
            "operation_id": operation_id,
            "method": method,
            "path": path,
            "path_params": path_params or {},
            "query": query or {},
            "body": body,
            "refresh_spec": refresh_spec,
        },
    )
    return json.dumps(result, ensure_ascii=False, indent=2)

