"""Tool schema definitions for OCLAWMA skills.

This module provides standardized schema definitions for tools,
enabling consistent validation, documentation, and token estimation.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from enum import Enum
from typing import Any, get_type_hints


class ParameterType(str, Enum):
    """Standard parameter types for tool schemas."""

    STRING = "string"
    INTEGER = "integer"
    NUMBER = "number"
    BOOLEAN = "boolean"
    ARRAY = "array"
    OBJECT = "object"
    NULL = "null"


class ReturnType(str, Enum):
    """Standard return types for tool schemas."""

    STRING = "string"
    INTEGER = "integer"
    NUMBER = "number"
    BOOLEAN = "boolean"
    ARRAY = "array"
    OBJECT = "object"
    NULL = "null"


@dataclass
class ToolParameter:
    """Definition of a tool parameter.

    Example:
        >>> param = ToolParameter(
        ...     name="force",
        ...     type=ParameterType.BOOLEAN,
        ...     description="Force the operation",
        ...     required=False,
        ...     default=False
        ... )
    """

    name: str
    type: ParameterType | str
    description: str = ""
    required: bool = True
    default: Any = None
    enum: list[Any] | None = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        result = {
            "name": self.name,
            "type": str(self.type),
            "description": self.description,
            "required": self.required,
        }
        if self.default is not None:
            result["default"] = self.default
        if self.enum:
            result["enum"] = self.enum
        return result

    def to_json_schema(self) -> dict[str, Any]:
        """Convert to JSON Schema property format."""
        result = {
            "type": str(self.type),
            "description": self.description,
        }
        if self.default is not None:
            result["default"] = self.default
        if self.enum:
            result["enum"] = self.enum
        return result

    def validate(self, value: Any) -> tuple[bool, str | None]:
        """Validate a value against this parameter.

        Args:
            value: The value to validate

        Returns:
            Tuple of (is_valid, error_message)
        """
        param_type = ParameterType(str(self.type))

        type_checks = {
            ParameterType.STRING: lambda v: isinstance(v, str),
            ParameterType.INTEGER: lambda v: isinstance(v, int) and not isinstance(v, bool),
            ParameterType.NUMBER: lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),
            ParameterType.BOOLEAN: lambda v: isinstance(v, bool),
            ParameterType.ARRAY: lambda v: isinstance(v, list),
            ParameterType.OBJECT: lambda v: isinstance(v, dict),
            ParameterType.NULL: lambda v: v is None,
        }

        if param_type not in type_checks:
            return False, f"Unknown parameter type: {self.type}"

        if not type_checks[param_type](value):
            return False, f"Expected {self.type}, got {type(value).__name__}"

        if self.enum and value not in self.enum:
            return False, f"Value must be one of: {self.enum}"

        return True, None


@dataclass
class ToolExample:
    """Example usage for a tool.

    Example:
        >>> example = ToolExample(
        ...     description="List all containers",
        ...     arguments={"all": True},
        ...     result={"containers": [...]}
        ... )
    """

    description: str
    arguments: dict[str, Any] = field(default_factory=dict)
    result: Any = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "description": self.description,
            "arguments": self.arguments,
            "result": self.result,
        }


@dataclass
class ToolReturns:
    """Definition of a tool's return value."""

    type: ReturnType | str
    description: str = ""
    schema: dict[str, Any] | None = None  # JSON Schema for complex returns

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        result = {
            "type": str(self.type),
            "description": self.description,
        }
        if self.schema:
            result["schema"] = self.schema
        return result


@dataclass
class ToolSchema:
    """Complete schema definition for a tool.

    This class provides a standardized way to define tool schemas
    that can be used for validation, documentation, and token estimation.

    Example:
        >>> schema = ToolSchema(
        ...     name="docker_ps",
        ...     description="List Docker containers",
        ...     parameters=[
        ...         ToolParameter(
        ...             name="all",
        ...             type=ParameterType.BOOLEAN,
        ...             description="Show all containers",
        ...             required=False,
        ...             default=False
        ...         ),
        ...     ],
        ...     returns=ToolReturns(
        ...         type=ReturnType.ARRAY,
        ...         description="List of container info"
        ...     ),
        ...     token_count=150
        ... )
    """

    name: str
    description: str
    parameters: list[ToolParameter] = field(default_factory=list)
    returns: ToolReturns | None = None
    examples: list[ToolExample] = field(default_factory=list)
    token_count: int = 0  # Estimated token count for this tool

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        result = {
            "name": self.name,
            "description": self.description,
            "parameters": [p.to_dict() for p in self.parameters],
        }
        if self.returns:
            result["returns"] = self.returns.to_dict()
        if self.examples:
            result["examples"] = [e.to_dict() for e in self.examples]
        if self.token_count:
            result["token_count"] = self.token_count
        return result

    def to_json_schema(self) -> dict[str, Any]:
        """Convert to full JSON Schema format."""
        properties = {}
        required = []

        for param in self.parameters:
            properties[param.name] = param.to_json_schema()
            if param.required:
                required.append(param.name)

        return {
            "name": self.name,
            "description": self.description,
            "parameters": {
                "type": "object",
                "properties": properties,
                "required": required,
            },
        }

    def validate_arguments(self, arguments: dict[str, Any]) -> tuple[bool, list[str]]:
        """Validate arguments against this schema.

        Args:
            arguments: The arguments to validate

        Returns:
            Tuple of (is_valid, list_of_errors)
        """
        errors = []

        # Check for unknown parameters
        param_names = {p.name for p in self.parameters}
        for arg_name in arguments:
            if arg_name not in param_names:
                errors.append(f"Unknown parameter: {arg_name}")

        # Validate each parameter
        for param in self.parameters:
            if param.name in arguments:
                is_valid, error = param.validate(arguments[param.name])
                if not is_valid:
                    errors.append(f"{param.name}: {error}")
            elif param.required:
                errors.append(f"Missing required parameter: {param.name}")

        return len(errors) == 0, errors

    def estimate_tokens(self) -> int:
        """Estimate the token count for this tool.

        Uses a simple heuristic: approximately 1 token per 4 characters
        in the schema description and parameters.

        Returns:
            Estimated token count
        """
        if self.token_count:
            return self.token_count

        # Rough estimation based on text length
        text = self.description
        for param in self.parameters:
            text += param.name + param.description

        return len(text) // 4 + 50  # Base overhead


@dataclass
class SkillSchema:
    """Complete schema for a skill containing multiple tools.

    Example:
        >>> skill_schema = SkillSchema(
        ...     name="docker",
        ...     version="1.0.0",
        ...     description="Docker container management",
        ...     tools=[
        ...         ToolSchema(...),
        ...         ToolSchema(...),
        ...     ]
        ... )
    """

    name: str
    version: str
    description: str = ""
    author: str = ""
    category: str = "general"
    tags: list[str] = field(default_factory=list)
    tools: list[ToolSchema] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary representation."""
        return {
            "name": self.name,
            "version": self.version,
            "description": self.description,
            "author": self.author,
            "category": self.category,
            "tags": self.tags,
            "tools": [t.to_dict() for t in self.tools],
        }

    def get_tool(self, name: str) -> ToolSchema | None:
        """Get a tool schema by name."""
        for tool in self.tools:
            if tool.name == name:
                return tool
        return None

    def estimate_tokens(self) -> int:
        """Estimate total token count for all tools in this skill."""
        return sum(t.estimate_tokens() for t in self.tools)


# Convenience functions for creating schemas


def create_tool_schema(
    name: str,
    description: str,
    parameters: list[dict[str, Any]] | None = None,
    returns_type: str = "string",
    returns_description: str = "",
    token_count: int = 0,
) -> ToolSchema:
    """Create a ToolSchema from simplified parameters.

    Args:
        name: Tool name
        description: Tool description
        parameters: List of parameter dictionaries
        returns_type: Return value type
        returns_description: Return value description
        token_count: Estimated token count

    Returns:
        ToolSchema instance

    Example:
        >>> schema = create_tool_schema(
        ...     name="hello",
        ...     description="Say hello",
        ...     parameters=[
        ...         {"name": "name", "type": "string", "description": "Name to greet", "required": False}
        ...     ],
        ...     returns_type="string",
        ...     returns_description="Greeting message",
        ...     token_count=50
        ... )
    """
    param_objects = []
    if parameters:
        for p in parameters:
            param_objects.append(
                ToolParameter(
                    name=p["name"],
                    type=p.get("type", "string"),
                    description=p.get("description", ""),
                    required=p.get("required", True),
                    default=p.get("default"),
                    enum=p.get("enum"),
                )
            )

    return ToolSchema(
        name=name,
        description=description,
        parameters=param_objects,
        returns=(
            ToolReturns(type=returns_type, description=returns_description)
            if returns_description
            else None
        ),
        token_count=token_count,
    )


def from_function(func, name: str | None = None) -> ToolSchema:
    """Create a ToolSchema from a Python function.

    Uses type hints and docstrings to infer the schema.

    Args:
        func: The function to introspect
        name: Optional override for the tool name

    Returns:
        ToolSchema derived from the function
    """
    import inspect

    tool_name = name or func.__name__
    description = func.__doc__ or f"Tool: {tool_name}"

    # Get type hints
    try:
        hints = get_type_hints(func)
    except Exception:
        hints = {}

    # Get signature
    sig = inspect.signature(func)

    parameters = []
    for param_name, param in sig.parameters.items():
        if param_name == "self":
            continue

        param_type = hints.get(param_name, str)
        type_map = {
            str: ParameterType.STRING,
            int: ParameterType.INTEGER,
            float: ParameterType.NUMBER,
            bool: ParameterType.BOOLEAN,
            list: ParameterType.ARRAY,
            dict: ParameterType.OBJECT,
            type(None): ParameterType.NULL,
        }

        schema_type = type_map.get(param_type, ParameterType.STRING)

        parameters.append(
            ToolParameter(
                name=param_name,
                type=schema_type,
                description="",  # Would need parsing from docstring
                required=param.default is inspect.Parameter.empty,
                default=param.default if param.default is not inspect.Parameter.empty else None,
            )
        )

    # Determine return type
    return_type = hints.get("return", str)
    returns_schema_type = type_map.get(return_type, ParameterType.STRING)

    return ToolSchema(
        name=tool_name,
        description=description,
        parameters=parameters,
        returns=ToolReturns(type=returns_schema_type),
    )
