import json
import logging
from enum import Enum
from typing import Any

from arcade.core.catalog import MaterializedTool

# Type aliases for MCP types
MCPTool = dict[str, Any]
MCPTextContent = dict[str, Any]
MCPImageContent = dict[str, Any]
MCPEmbeddedResource = dict[str, Any]
MCPContent = MCPTextContent | MCPImageContent | MCPEmbeddedResource

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


def create_mcp_tool(tool: MaterializedTool) -> dict[str, Any] | None:  # noqa: C901
    """
    Create an MCP-compatible tool definition from an Arcade tool.

    Args:
        tool: An Arcade tool object

    Returns:
        An MCP tool definition or None if the tool cannot be converted
    """
    try:
        name = getattr(tool.definition, "fully_qualified_name", None) or getattr(
            tool.definition, "name", "unknown"
        )
        description = getattr(tool.definition, "description", "No description available")

        # Extract parameters from the input model
        parameters = {}
        required = []

        if (
            hasattr(tool, "input_model")
            and tool.input_model is not None
            and hasattr(tool.input_model, "model_fields")
        ):
            for field_name, field in tool.input_model.model_fields.items():
                # Skip internal tool context parameters
                if field_name == getattr(
                    tool.definition.input, "tool_context_parameter_name", None
                ):
                    continue

                # Get field type information
                field_type = getattr(field, "annotation", None)
                field_type_name = "string"  # default

                # Safety check for field_type
                if field_type is int:
                    field_type_name = "integer"
                elif field_type is float:
                    field_type_name = "number"
                elif field_type is bool:
                    field_type_name = "boolean"
                elif field_type is list or str(field_type).startswith("list["):
                    field_type_name = "array"
                elif field_type is dict or str(field_type).startswith("dict["):
                    field_type_name = "object"

                # Get description with fallback
                field_description = getattr(field, "description", None)
                if not field_description:
                    field_description = f"Parameter: {field_name}"

                # Create parameter definition
                param_def = {
                    "type": field_type_name,
                    "description": field_description,
                }

                # Enum support: if the field annotation is an Enum, add allowed values
                enum_type = None
                if hasattr(field, "annotation"):
                    ann = field.annotation
                    # Handle typing.Annotated[Enum, ...]
                    if getattr(ann, "__origin__", None) is not None and hasattr(ann, "__args__"):
                        for arg in ann.__args__:  # type: ignore[union-attr]
                            if isinstance(arg, type) and issubclass(arg, Enum):
                                enum_type = arg
                                break
                    elif isinstance(ann, type) and issubclass(ann, Enum):
                        enum_type = ann
                if enum_type is not None:
                    param_def["enum"] = [e.value for e in enum_type]

                parameters[field_name] = param_def

                # In Pydantic v2, check if field is required based on default value
                try:
                    if field.is_required():
                        required.append(field_name)
                except (AttributeError, TypeError):
                    # Fallback if is_required() doesn't exist or fails
                    try:
                        has_default = getattr(field, "default", None) is not None
                        has_factory = getattr(field, "default_factory", None) is not None
                        if not (has_default or has_factory):
                            required.append(field_name)
                    except Exception:
                        # Ultimate fallback - assume required if we can't determine
                        logger.debug(
                            f"Could not determine if field {field_name} is required, assuming optional"
                        )

        # Create the input schema with explicit properties and required fields
        input_schema = {
            "type": "object",
            "properties": parameters,
        }

        # Only include required field if we have required parameters
        if required:
            input_schema["required"] = required

        # Add annotations based on tool metadata
        annotations = {}

        # Use tool name as title if available
        annotations["title"] = getattr(tool.definition, "title", str(name).replace(".", "_"))

        # Determine hints based on tool properties
        if hasattr(tool.definition, "metadata"):
            metadata = tool.definition.metadata or {}
            annotations["readOnlyHint"] = metadata.get("read_only", False)
            annotations["destructiveHint"] = metadata.get("destructive", False)
            annotations["idempotentHint"] = metadata.get("idempotent", True)
            annotations["openWorldHint"] = metadata.get("open_world", False)

        # Create the final tool definition
        tool_def: MCPTool = {
            "name": str(name).replace(".", "_"),
            "description": str(description),
            "inputSchema": input_schema,
            "annotations": annotations,
        }

        logger.debug(f"Created tool definition for {name}")

    except Exception:
        logger.exception(
            f"Error creating MCP tool definition for {getattr(tool, 'name', str(tool))}"
        )
        return None
    return tool_def


def convert_to_mcp_content(value: Any) -> list[dict[str, Any]]:
    """
    Convert a Python value to MCP-compatible content.
    """
    if value is None:
        return []

    if isinstance(value, (str, bool, int, float)):
        return [{"type": "text", "text": str(value)}]

    if isinstance(value, (dict, list)):
        return [{"type": "text", "text": json.dumps(value)}]

    # Default fallback
    return [{"type": "text", "text": str(value)}]


def _map_type_to_json_schema_type(val_type: str) -> str:
    """
    Map Arcade value types to JSON schema types.

    Args:
        val_type: The Arcade value type as a string.

    Returns:
        The corresponding JSON schema type as a string.
    """
    mapping: dict[str, str] = {
        "string": "string",
        "integer": "integer",
        "number": "number",
        "boolean": "boolean",
        "json": "object",
        "array": "array",
    }
    return mapping.get(val_type, "string")
