"""
OpenAI adapter for Boring-OS.

Provides OpenAI-compatible function/tool definitions and utilities
for executing tool calls from OpenAI responses.

Usage:
    from boring_os import BoringOS
    from boring_os.adapters.openai import get_openai_tools, execute_tool_call
    
    client = BoringOS(api_key="...")
    tools = get_openai_tools(client)
    
    # Use with OpenAI
    response = openai.chat.completions.create(
        model="gpt-4",
        messages=messages,
        tools=tools
    )
    
    # Execute tool calls
    for tool_call in response.choices[0].message.tool_calls:
        result = execute_tool_call(client, tool_call)

Requires: pip install boring-os[openai]
"""

from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
    from boring_os.client import BoringOS
    from boring_os.models import Tool


def _json_schema_to_openai(json_schema: dict[str, Any]) -> dict[str, Any]:
    """
    Convert JSON Schema to OpenAI function parameters format.
    
    OpenAI uses a subset of JSON Schema for function parameters.
    
    Args:
        json_schema: JSON Schema object
    
    Returns:
        OpenAI-compatible parameters schema
    """
    openai_schema: dict[str, Any] = {}
    
    # Map type
    if "type" in json_schema:
        openai_schema["type"] = json_schema["type"]
    
    # Map description
    if "description" in json_schema:
        openai_schema["description"] = json_schema["description"]
    
    # Map enum
    if "enum" in json_schema:
        openai_schema["enum"] = json_schema["enum"]
    
    # Map object properties
    if "properties" in json_schema:
        openai_schema["properties"] = {
            name: _json_schema_to_openai(prop)
            for name, prop in json_schema["properties"].items()
        }
    
    # Map required fields
    if "required" in json_schema:
        openai_schema["required"] = json_schema["required"]
    
    # Map array items
    if "items" in json_schema:
        openai_schema["items"] = _json_schema_to_openai(json_schema["items"])
    
    return openai_schema


def _create_openai_tool(tool: "Tool") -> dict[str, Any]:
    """
    Create an OpenAI tool definition from a Boring-OS tool.
    
    Args:
        tool: Tool definition
    
    Returns:
        OpenAI tool definition
    """
    # Build description
    description = tool.description or f"Execute the {tool.name} tool"
    if tool.instructions:
        if tool.instructions.summary:
            description = tool.instructions.summary
        if tool.instructions.when_to_use:
            description += f" Use when: {tool.instructions.when_to_use}"
    
    # Build parameters from input schema
    parameters: dict[str, Any] = {
        "type": "object",
        "properties": {},
        "required": [],
    }
    
    if tool.schema_ and tool.schema_.input:
        input_schema = tool.schema_.input
        if "properties" in input_schema:
            parameters["properties"] = {
                name: _json_schema_to_openai(prop)
                for name, prop in input_schema["properties"].items()
            }
        if "required" in input_schema:
            parameters["required"] = input_schema["required"]
    
    # OpenAI tool format
    return {
        "type": "function",
        "function": {
            "name": tool.name,
            "description": description,
            "parameters": parameters,
        }
    }


def get_openai_tools(
    client: "BoringOS",
    tool_names: list[str] | None = None,
    strict: bool = False,
) -> list[dict[str, Any]]:
    """
    Get OpenAI-compatible tool definitions from Boring-OS.
    
    Returns tool definitions in the format expected by the OpenAI
    Chat Completions API tools parameter.
    
    Args:
        client: Boring-OS client instance
        tool_names: Optional list of specific tool names
        strict: If True, add strict mode for structured outputs
    
    Returns:
        List of OpenAI tool definitions
    
    Example:
        from boring_os import BoringOS
        from boring_os.adapters.openai import get_openai_tools
        
        client = BoringOS(api_key="...")
        tools = get_openai_tools(client)
        
        # Use with OpenAI
        import openai
        response = openai.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": "Get customer 123's profile"}],
            tools=tools
        )
    """
    from boring_os.models import ToolStatus
    
    # Fetch tool definitions
    tool_defs: list["Tool"] = []
    
    if tool_names:
        for name in tool_names:
            try:
                tool_def = client.tools.get(name)
                tool_defs.append(tool_def)
            except Exception:
                pass
    else:
        response = client.tools.list(status=ToolStatus.ACTIVE, limit=1000)
        for tool_info in response.tools:
            try:
                tool_def = client.tools.get(tool_info.name)
                tool_defs.append(tool_def)
            except Exception:
                pass
    
    # Convert to OpenAI format
    openai_tools = []
    for tool in tool_defs:
        openai_tool = _create_openai_tool(tool)
        
        # Add strict mode if requested (for structured outputs)
        if strict:
            openai_tool["function"]["strict"] = True
        
        openai_tools.append(openai_tool)
    
    return openai_tools


def execute_tool_call(
    client: "BoringOS",
    tool_call: Any,
    timeout: int = 30,
) -> dict[str, Any]:
    """
    Execute a tool call from an OpenAI response.
    
    Takes a tool_call object from OpenAI's response and executes
    the corresponding Boring-OS tool.
    
    Args:
        client: Boring-OS client instance
        tool_call: Tool call object from OpenAI response
        timeout: Execution timeout in seconds
    
    Returns:
        Tool execution result
    
    Example:
        response = openai.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=tools
        )
        
        if response.choices[0].message.tool_calls:
            for tool_call in response.choices[0].message.tool_calls:
                result = execute_tool_call(client, tool_call)
                
                # Add result to messages for follow-up
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": json.dumps(result)
                })
    """
    # Extract tool name and arguments
    function = tool_call.function
    tool_name = function.name
    
    # Parse arguments (may be JSON string or dict)
    if isinstance(function.arguments, str):
        arguments = json.loads(function.arguments)
    else:
        arguments = function.arguments or {}
    
    # Execute the tool
    return client.tools.call(tool_name, timeout=timeout, **arguments)


async def execute_tool_call_async(
    client: "BoringOS",
    tool_call: Any,
    timeout: int = 30,
) -> dict[str, Any]:
    """
    Async version of execute_tool_call.
    
    Args:
        client: Boring-OS client instance
        tool_call: Tool call object from OpenAI response
        timeout: Execution timeout in seconds
    
    Returns:
        Tool execution result
    """
    function = tool_call.function
    tool_name = function.name
    
    if isinstance(function.arguments, str):
        arguments = json.loads(function.arguments)
    else:
        arguments = function.arguments or {}
    
    return await client.tools.call_async(tool_name, timeout=timeout, **arguments)


def create_tool_result_message(
    tool_call_id: str,
    result: Any,
) -> dict[str, Any]:
    """
    Create an OpenAI tool result message.
    
    Helper to format tool results for adding back to the messages list.
    
    Args:
        tool_call_id: ID from the tool_call object
        result: Tool execution result
    
    Returns:
        Message dict for OpenAI messages list
    
    Example:
        for tool_call in response.choices[0].message.tool_calls:
            result = execute_tool_call(client, tool_call)
            messages.append(create_tool_result_message(tool_call.id, result))
    """
    content = json.dumps(result) if not isinstance(result, str) else result
    
    return {
        "role": "tool",
        "tool_call_id": tool_call_id,
        "content": content,
    }


def get_function_definitions(
    client: "BoringOS",
    tool_names: list[str] | None = None,
) -> list[dict[str, Any]]:
    """
    Get legacy OpenAI function definitions (deprecated format).
    
    Returns function definitions in the older functions format.
    Use get_openai_tools() for the newer tools format.
    
    Args:
        client: Boring-OS client instance
        tool_names: Optional list of specific tool names
    
    Returns:
        List of function definitions
    """
    tools = get_openai_tools(client, tool_names)
    
    # Extract just the function definitions
    return [tool["function"] for tool in tools]
