"""Tool Calling Utilities — helpers for LLM function calling.

Provides utilities for:
- Getting tool schemas as LLMToolSpec Pydantic models
- Converting tool names between formats
- Executing tool calls via the registry

ALL PYDANTIC — LLMToolSpec models, ToolCall models, ToolExecutionResultItem models.
"""

from __future__ import annotations

import json
import logging
from typing import TYPE_CHECKING

from core.pathways.models.tool import (
    ToolArgs,
    ToolCall,
    ToolCallPlan,
    ToolExecutionResult,
    ToolResult,
)
from core.pathways.nodes.models import ToolExecutionResultItem
from core.tools import get_tool_registry
from core.types.llm import LLMFunctionParameters, LLMFunctionSpec, LLMToolSpec

if TYPE_CHECKING:
    from core.pathways.context import Context

logger = logging.getLogger(__name__)


def get_tool_schemas_for_names(
    tool_names: list[str],
    ctx: Context,
) -> list[LLMToolSpec]:
    """Get LLMToolSpec models for the given tool names.

    Supports wildcards like "pathway.*" to include all pathway tools.

    Args:
        tool_names: List of tool names or patterns (e.g., ["web.*", "pathway.create"])
        ctx: Execution context with tools and definitions

    Returns
    -------
        List of LLMToolSpec Pydantic models in OpenAI function calling format
    """
    # Get tool definitions from global registry
    registry = get_tool_registry()
    tool_definitions = {
        td.name: {"description": td.description, "parameters": td.parameters}
        for td in registry.list()
    }

    # Get available tools from both context and registry
    available = sorted({*ctx.tools.keys(), *tool_definitions.keys()})

    # Expand wildcards
    expanded_names: list[str] = []
    for name in tool_names:
        if name.endswith(".*"):
            prefix = name[:-2]
            expanded_names.extend(t for t in available if t.startswith(f"{prefix}."))
        elif name == "*":
            expanded_names.extend(available)
        elif name in available:
            expanded_names.append(name)

    # Build schemas as Pydantic models
    schemas: list[LLMToolSpec] = []
    for name in expanded_names:
        if (
            tool_definitions
            and name in tool_definitions
            and isinstance(tool_definitions[name], dict)
        ):
            defn = tool_definitions[name]
            # Sanitize name for OpenAI (no dots)
            safe_name = name.replace(".", "_")
            raw_params = defn.get("parameters") or {"type": "object", "properties": {}, "required": []}

            # Build typed parameter model
            if isinstance(raw_params, dict):
                params = LLMFunctionParameters.model_validate(raw_params)
            else:
                params = LLMFunctionParameters()

            schema = LLMToolSpec(
                function=LLMFunctionSpec(
                    name=safe_name,
                    description=str(defn.get("description", "") or ""),
                    parameters=params,
                ),
            )
            schemas.append(schema)
        else:
            # Fallback to minimal schema
            schema = _get_fallback_schema(name)
            if schema:
                schemas.append(schema)

    return schemas


def _get_fallback_schema(name: str) -> LLMToolSpec | None:
    """Get fallback LLMToolSpec for common tools."""
    safe_name = name.replace(".", "_")
    return LLMToolSpec(
        function=LLMFunctionSpec(
            name=safe_name,
            description=f"Call the {name} tool",
            parameters=LLMFunctionParameters(),
        ),
    )


def _openai_name_to_tool_name(openai_name: str) -> str:
    """Convert OpenAI function name back to tool name.

    Example: "pathway_create" -> "pathway.create"
    """
    parts = openai_name.split("_", 1)
    if len(parts) == 2:
        return f"{parts[0]}.{parts[1]}"
    return openai_name


async def execute_tool_calls(
    tool_calls: list[ToolCall],
    ctx: Context,
) -> list[ToolExecutionResultItem]:
    """Execute tool calls via the global registry.

    Converts each ToolCall into ToolArgs, invokes the registry,
    and returns typed ToolExecutionResultItem models.
    """
    results: list[ToolExecutionResultItem] = []
    registry = get_tool_registry()

    for tc in tool_calls:
        tool_name = _openai_name_to_tool_name(tc.tool)
        raw_args = tc.args
        if isinstance(raw_args, str):
            raw_args = json.loads(raw_args)
        tool_args = ToolArgs.model_validate(raw_args or {})

        out = await registry.call(tool_name, tool_args, ctx)
        results.append(
            ToolExecutionResultItem(
                tool=tool_name,
                success=out.success,
                output=out.data,
                error=out.error,
            )
        )

    return results


__all__ = [
    # DTOs
    "ToolCall",
    "ToolCallPlan",
    "ToolExecutionResult",
    "ToolResult",
    # Utilities
    "_openai_name_to_tool_name",
    "execute_tool_calls",
    "get_tool_schemas_for_names",
]
