"""Schema generation utilities for tool creation and updates."""

from typing import Optional

from letta.functions.ast_parsers import get_function_name_and_docstring
from letta.functions.functions import derive_openai_json_schema
from letta.functions.helpers import generate_model_from_args_json_schema
from letta.functions.schema_generator import generate_schema_from_args_schema_v2
from letta.log import get_logger
from letta.otel.tracing import trace_method
from letta.schemas.enums import ToolSourceType, ToolType
from letta.schemas.tool import Tool as PydanticTool

logger = get_logger(__name__)


@trace_method
def generate_schema_for_tool_creation(
    tool: PydanticTool,
) -> Optional[dict]:
    """
    Generate JSON schema for tool creation based on the provided parameters.

    Args:
        tool: The tool being created

    Returns:
        Generated JSON schema or None if not applicable
    """
    # Only generate schema for custom tools
    if tool.tool_type != ToolType.CUSTOM:
        return None

    # If json_schema is already provided, use it
    if tool.json_schema:
        return tool.json_schema

    # Must have source code for custom tools
    if not tool.source_code:
        logger.error("Custom tool is missing source_code field")
        raise ValueError("Custom tool is missing source_code field.")

    source_code_size_kb = len(tool.source_code) / 1024
    logger.info(f"Generating schema for tool '{tool.name}': source code {source_code_size_kb:.2f} KB")

    # TypeScript tools
    if tool.source_type == ToolSourceType.typescript:
        try:
            from letta.functions.typescript_parser import derive_typescript_json_schema
            from letta.otel.tracing import tracer

            with tracer.start_as_current_span("derive_typescript_json_schema"):
                schema = derive_typescript_json_schema(source_code=tool.source_code)
            import json

            schema_size_kb = len(json.dumps(schema)) / 1024
            logger.info(f"Generated TypeScript schema for '{tool.name}': {schema_size_kb:.2f} KB")
            return schema
        except Exception as e:
            logger.warning(f"Failed to derive TypeScript json schema: {e}")
            raise ValueError(f"Failed to derive TypeScript json schema: {e}")

    # Python tools (default if not specified for backwards compatibility)
    elif tool.source_type == ToolSourceType.python or tool.source_type is None:
        from letta.otel.tracing import tracer

        # If args_json_schema is provided, use it to generate full schema
        if tool.args_json_schema:
            with tracer.start_as_current_span("get_function_name_and_docstring"):
                name, description = get_function_name_and_docstring(tool.source_code, tool.name)
            with tracer.start_as_current_span("generate_model_from_args_json_schema"):
                args_schema = generate_model_from_args_json_schema(tool.args_json_schema)
            with tracer.start_as_current_span("generate_schema_from_args_schema_v2"):
                schema = generate_schema_from_args_schema_v2(
                    args_schema=args_schema,
                    name=name,
                    description=description,
                    append_heartbeat=False,
                )
            import json

            schema_size_kb = len(json.dumps(schema)) / 1024
            logger.info(f"Generated Python schema from args_json for '{tool.name}': {schema_size_kb:.2f} KB")
            return schema
        # Otherwise, attempt to parse from docstring with best effort
        else:
            try:
                with tracer.start_as_current_span("derive_openai_json_schema"):
                    schema = derive_openai_json_schema(source_code=tool.source_code)
                import json

                schema_size_kb = len(json.dumps(schema)) / 1024
                logger.info(f"Generated Python schema from docstring for '{tool.name}': {schema_size_kb:.2f} KB")
                return schema
            except Exception as e:
                logger.warning(f"Failed to derive json schema: {e}")
                raise ValueError(f"Failed to derive json schema: {e}")
    else:
        # TODO: convert to explicit error
        raise ValueError(f"Unknown tool source type: {tool.source_type}")


def generate_schema_for_tool_update(
    current_tool: PydanticTool,
    json_schema: Optional[dict] = None,
    args_json_schema: Optional[dict] = None,
    source_code: Optional[str] = None,
    source_type: Optional[ToolSourceType] = None,
) -> Optional[dict]:
    """
    Generate JSON schema for tool update based on the provided parameters.

    Args:
        current_tool: The current tool being updated
        json_schema: Directly provided JSON schema (takes precedence)
        args_json_schema: Schema for just the arguments
        source_code: Updated source code (only used if explicitly updating source)
        source_type: Source type for the tool

    Returns:
        Updated JSON schema or None if no update needed
    """
    # Only handle custom tools
    if current_tool.tool_type != ToolType.CUSTOM:
        return None

    # If json_schema is directly provided, use it
    if json_schema is not None:
        # If args_json_schema is also provided, that's an error
        if args_json_schema is not None:
            raise ValueError("Cannot provide both json_schema and args_json_schema in update")
        return json_schema

    # If args_json_schema is provided, generate full schema from it
    if args_json_schema is not None:
        # Use updated source_code if provided, otherwise use current
        code_to_parse = source_code if source_code is not None else current_tool.source_code
        if not code_to_parse:
            raise ValueError("Source code required when updating with args_json_schema")

        name, description = get_function_name_and_docstring(code_to_parse, current_tool.name)
        args_schema = generate_model_from_args_json_schema(args_json_schema)
        return generate_schema_from_args_schema_v2(
            args_schema=args_schema,
            name=name,
            description=description,
            append_heartbeat=False,
        )

    # Otherwise, no schema updates (don't parse docstring)
    return None
