"""
Tests for executor: OpenAI function calls  Ape runtime.
"""

import pytest
import json
from unittest.mock import Mock, MagicMock, patch

from ape_openai.executor import (
    execute_openai_call,
    ApeOpenAIFunction
)
from ape_openai import ApeTask, execute_openai_function_call


def test_execute_openai_call_success():
    """Test successful execution of OpenAI call."""
    # Mock Ape module with proper signature
    mock_signature = Mock()
    mock_signature.inputs = {"a": "int", "b": "int"}

    mock_module = Mock()
    mock_module.call.return_value = 42
    mock_module.get_function_signature.return_value = mock_signature

    arguments_json = '{"a": 10, "b": 32}'
    result = execute_openai_call(mock_module, "add", arguments_json)

    assert result == 42
    mock_module.call.assert_called_once_with("add", a=10, b=32)


def test_execute_openai_call_invalid_json():
    """Test handling of invalid JSON."""
    mock_module = Mock()

    arguments_json = 'invalid json{'

    with pytest.raises(json.JSONDecodeError, match="Invalid JSON"):
        execute_openai_call(mock_module, "add", arguments_json)


def test_execute_openai_call_missing_required_argument():
    """Test handling of missing required parameter."""
    mock_signature = Mock()
    mock_signature.inputs = {"a": "int", "b": "int"}

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature

    # Missing 'b' parameter
    arguments_json = '{"a": 10}'

    with pytest.raises(TypeError, match="Missing required parameters"):
        execute_openai_call(mock_module, "add", arguments_json)


def test_execute_openai_call_execution_error():
    """Test handling of execution error."""
    mock_signature = Mock()
    mock_signature.inputs = {"x": "int"}

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature
    mock_module.call.side_effect = Exception("Execution failed")

    arguments_json = '{"x": 5}'

    with pytest.raises(Exception, match="Execution failed"):
        execute_openai_call(mock_module, "calculate", arguments_json)


def test_execute_openai_call_empty_arguments():
    """Test execution with empty arguments."""
    mock_signature = Mock()
    mock_signature.inputs = {}

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature
    mock_module.call.return_value = "timestamp"

    arguments_json = '{}'
    result = execute_openai_call(mock_module, "get_timestamp", arguments_json)

    assert result == "timestamp"
    mock_module.call.assert_called_once_with("get_timestamp")


def test_ape_openai_function_from_ape_file():
    """Test creating ApeOpenAIFunction from Ape file."""
    # Mock the compile function at module level
    with patch('ape_openai.executor.ape_compile') as mock_compile:
        mock_module = Mock()
        mock_compile.return_value = mock_module

        # Mock function signature
        mock_sig = Mock()
        mock_sig.name = "add"
        mock_sig.inputs = {"a": "int", "b": "int"}
        mock_sig.output = "int"
        mock_sig.description = "Add two numbers"

        mock_module.get_function_signature.return_value = mock_sig

        ape_func = ApeOpenAIFunction.from_ape_file("add.ape", "add")

        assert ape_func.function_name == "add"
        assert ape_func.signature.name == "add"
        mock_compile.assert_called_once_with("add.ape")


def test_ape_openai_function_to_openai_tool():
    """Test converting ApeOpenAIFunction to OpenAI tool schema."""
    # Create properly structured mock module
    mock_signature = Mock()
    mock_signature.name = "multiply"
    mock_signature.inputs = {"x": "int", "y": "int"}
    mock_signature.output = "int"
    mock_signature.description = "Multiply two numbers"

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature

    ape_func = ApeOpenAIFunction(mock_module, "multiply", "Multiply two numbers")
    tool_schema = ape_func.to_openai_tool()

    assert tool_schema["type"] == "function"
    assert tool_schema["function"]["name"] == "multiply"
    assert tool_schema["function"]["description"] == "Multiply two numbers"


def test_ape_openai_function_execute():
    """Test executing ApeOpenAIFunction."""
    mock_signature = Mock()
    mock_signature.name = "multiply"
    mock_signature.inputs = {"x": "int", "y": "int"}

    mock_module = Mock()
    mock_module.call.return_value = 100
    mock_module.get_function_signature.return_value = mock_signature

    ape_func = ApeOpenAIFunction(mock_module, "multiply")

    arguments_json = '{"x": 10, "y": 10}'
    result = ape_func.execute(arguments_json)

    assert result == 100
    mock_module.call.assert_called_once_with("multiply", x=10, y=10)


def test_ape_openai_function_execute_with_validation_error():
    """Test execution with validation error."""
    from ape import ApeExecutionError

    mock_signature = Mock()
    mock_signature.name = "calculate"
    mock_signature.inputs = {"value": "int"}

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature
    mock_module.call.side_effect = TypeError("Wrong type")

    ape_func = ApeOpenAIFunction(mock_module, "calculate")

    arguments_json = '{"value": "not an int"}'

    with pytest.raises(ApeExecutionError):
        ape_func.execute(arguments_json)


def test_execute_openai_call_with_nested_json():
    """Test execution with nested JSON arguments."""
    mock_signature = Mock()
    mock_signature.inputs = {"config": "dict", "items": "list"}

    mock_module = Mock()
    mock_module.get_function_signature.return_value = mock_signature
    mock_module.call.return_value = {"status": "success"}

    arguments_json = json.dumps({
        "config": {
            "threshold": 0.5,
            "enabled": True
        },
        "items": [1, 2, 3]
    })

    result = execute_openai_call(mock_module, "process", arguments_json)

    assert result == {"status": "success"}
    mock_module.call.assert_called_once()


def test_ape_openai_function_missing_function():
    """Test error when function doesn't exist in module."""
    with patch('ape_openai.executor.ape_compile') as mock_compile:
        mock_module = Mock()
        mock_module.get_function_signature.side_effect = KeyError("Function not found")
        mock_module.list_functions.return_value = ["func1", "func2"]
        mock_compile.return_value = mock_module

        with pytest.raises(KeyError):
            ApeOpenAIFunction.from_ape_file("test.ape", "nonexistent_func")


def test_execute_openai_function_call_with_task():
    """Test execute_openai_function_call alias with ApeTask validation."""
    task = ApeTask(
        name="add",
        inputs={"a": "int", "b": "int"}
    )

    mock_module = Mock()
    mock_module.call.return_value = 15
    
    mock_signature = Mock()
    mock_signature.inputs = {"a": "int", "b": "int"}
    mock_module.get_function_signature.return_value = mock_signature

    result = execute_openai_function_call(
        mock_module,
        task,
        '{"a": 7, "b": 8}'
    )

    assert result == 15


def test_execute_openai_function_call_missing_required():
    """Test execute_openai_function_call raises error on missing parameter."""
    task = ApeTask(
        name="add",
        inputs={"a": "int", "b": "int"}
    )

    mock_module = Mock()

    with pytest.raises(TypeError, match="Missing required parameters"):
        execute_openai_function_call(
            mock_module,
            task,
            '{"a": 7}'
        )
