"""Tests for tool module."""

import pytest

from iq_sdk.properties import (
    DataType,
    ExposureCategory,
    Property,
    PropertyRegistry,
)
from iq_sdk.tool import (
    BaseTool,
    ToolGeneral,
    ToolInput,
    ToolModelFactory,
    ToolOutput,
    ToolResult,
)

# ============================================================================
# TEST FIXTURES - Property-based tool
# ============================================================================

def create_test_properties() -> PropertyRegistry:
    """Create a property registry for testing."""
    props = PropertyRegistry()

    # General properties
    props.add(Property(
        name="mode",
        data_type=DataType.STRING,
        categories=[ExposureCategory.GENERAL],
        required=False,
        default="default",
    ))
    props.add(Property(
        name="priority",
        data_type=DataType.INTEGER,
        categories=[ExposureCategory.GENERAL],
        required=False,
        default=5,
    ))

    # Input properties
    props.add(Property(
        name="data",
        data_type=DataType.STRING,
        categories=[ExposureCategory.INPUT],
        required=True,
    ))
    props.add(Property(
        name="count",
        data_type=DataType.INTEGER,
        categories=[ExposureCategory.INPUT],
        required=False,
        default=1,
    ))

    # Output properties
    props.add(Property(
        name="result",
        data_type=DataType.STRING,
        categories=[ExposureCategory.OUTPUT],
        required=False,
        default="",
    ))
    props.add(Property(
        name="status",
        data_type=DataType.STRING,
        categories=[ExposureCategory.OUTPUT],
        required=False,
        default="pending",
    ))

    return props


class SampleTool_1_0(BaseTool):
    """Sample tool for testing."""

    properties = create_test_properties()

    async def process(self) -> None:
        # Access via self - this instance is ours
        data = self.input.data
        count = self.input.count
        mode = self.general.mode

        # Modify pre-filled output directly
        self.output.result = f"{data} x {count} ({mode})"
        self.output.status = "completed"


class FailingTool_1_0(BaseTool):
    """Tool that always fails."""

    properties = create_test_properties()

    async def process(self) -> None:
        raise ValueError("Always fails")


class StatefulTool_1_0(BaseTool):
    """Tool that tracks call count to verify isolation."""

    properties = create_test_properties()

    # Class-level counter (shared)
    call_count: int = 0

    async def process(self) -> None:
        StatefulTool_1_0.call_count += 1
        # Store the count at time of this call
        self.output.result = f"call_{StatefulTool_1_0.call_count}"
        self.output.status = "completed"


# ============================================================================
# TESTS
# ============================================================================

class TestToolModelFactory:
    """Test ToolModelFactory class."""

    def test_factory_creates_models(self) -> None:
        """Test factory creates models for each category."""
        props = create_test_properties()
        factory = ToolModelFactory.for_registry(props)

        assert factory.has_category(ExposureCategory.GENERAL)
        assert factory.has_category(ExposureCategory.INPUT)
        assert factory.has_category(ExposureCategory.OUTPUT)
        assert not factory.has_category(ExposureCategory.API_INPUT)

    def test_factory_is_cached(self) -> None:
        """Test factory is cached per PropertyRegistry."""
        props = create_test_properties()
        factory1 = ToolModelFactory.for_registry(props)
        factory2 = ToolModelFactory.for_registry(props)

        assert factory1 is factory2

    def test_create_instance_validates(self) -> None:
        """Test create_instance validates data."""
        props = create_test_properties()
        factory = ToolModelFactory.for_registry(props)

        instance = factory.create_instance(
            ExposureCategory.INPUT,
            {"data": "test", "count": 5},
        )

        assert instance.data == "test"
        assert instance.count == 5

    def test_create_instance_uses_defaults(self) -> None:
        """Test create_instance uses defaults."""
        props = create_test_properties()
        factory = ToolModelFactory.for_registry(props)

        instance = factory.create_instance(
            ExposureCategory.GENERAL,
            {},
        )

        assert instance.mode == "default"
        assert instance.priority == 5


class TestBaseTool:
    """Test BaseTool class."""

    @pytest.mark.asyncio
    async def test_execute_success(self) -> None:
        """Test successful tool execution."""
        result = await SampleTool_1_0.execute({
            "input": {"data": "test", "count": 3},
            "general": {"mode": "turbo"},
        })

        assert result.success is True
        assert result.output is not None
        assert result.output.result == "test x 3 (turbo)"
        assert result.output.status == "completed"
        assert result.error is None
        assert result.duration_ms >= 0

    @pytest.mark.asyncio
    async def test_execute_with_defaults(self) -> None:
        """Test execution with default values."""
        result = await SampleTool_1_0.execute({
            "input": {"data": "hello"},
        })

        assert result.success is True
        assert result.output.result == "hello x 1 (default)"

    @pytest.mark.asyncio
    async def test_execute_failure(self) -> None:
        """Test failed tool execution (exception caught)."""
        result = await FailingTool_1_0.execute({
            "input": {"data": "test"},
        })

        assert result.success is False
        assert result.output is None
        assert "Always fails" in result.error
        assert result.error_traceback is not None

    @pytest.mark.asyncio
    async def test_each_call_gets_new_instance(self) -> None:
        """Test that each execute creates a new instance."""
        # Reset counter
        StatefulTool_1_0.call_count = 0

        result1 = await StatefulTool_1_0.execute({"input": {"data": "a"}})
        result2 = await StatefulTool_1_0.execute({"input": {"data": "b"}})
        result3 = await StatefulTool_1_0.execute({"input": {"data": "c"}})

        # Each call should have gotten its own output
        assert result1.output.result == "call_1"
        assert result2.output.result == "call_2"
        assert result3.output.result == "call_3"

    @pytest.mark.asyncio
    async def test_output_prefilled_with_defaults(self) -> None:
        """Test output is pre-filled with defaults."""
        # Create a tool that doesn't modify all output fields
        class PartialOutputTool_1_0(BaseTool):
            properties = create_test_properties()

            async def process(self) -> None:
                # Only modify result, not status
                self.output.result = "done"

        result = await PartialOutputTool_1_0.execute({"input": {"data": "x"}})

        assert result.output.result == "done"
        assert result.output.status == "pending"  # Default preserved

    def test_tool_name_auto_generated(self) -> None:
        """Test tool name is auto-generated from class name."""
        tool = SampleTool_1_0()

        assert tool._get_base_name() == "sample_tool"
        assert tool.version == "1.0"
        assert tool.major_version == 1
        assert tool.minor_version == 0

    def test_tool_properties(self) -> None:
        """Test tool property methods."""
        tool = SampleTool_1_0()

        assert "sample_tool" in tool.name
        assert "1.0" in tool.name
        assert tool.description == "Sample tool for testing."

    def test_to_info(self) -> None:
        """Test tool info export."""
        tool = SampleTool_1_0()
        info = tool.to_info()

        assert "sample_tool" in info["name"]
        assert info["base_name"] == "sample_tool"
        assert info["version"] == "1.0"
        assert "general" in info["categories"]
        assert "input" in info["categories"]
        assert "output" in info["categories"]

    def test_to_kwargs(self) -> None:
        """Test to_kwargs conversion."""
        tool = SampleTool_1_0()
        tool.general = ToolGeneral()
        tool.input = ToolInput()

        kwargs = tool.to_kwargs()

        assert "general" in kwargs
        assert "input" in kwargs
        assert "output" not in kwargs  # None

    def test_get_category(self) -> None:
        """Test get by category."""
        tool = SampleTool_1_0()
        general = ToolGeneral()
        tool.general = general

        assert tool.get(ExposureCategory.GENERAL) is general
        assert tool.get(ExposureCategory.INPUT) is None

    @pytest.mark.asyncio
    async def test_metadata_preserved(self) -> None:
        """Test metadata is preserved through execution."""
        result = await SampleTool_1_0.execute(
            {"input": {"data": "test"}},
            metadata={"request_id": "abc123"},
        )

        assert result.metadata["request_id"] == "abc123"

    @pytest.mark.asyncio
    async def test_execute_empty_data(self) -> None:
        """Test execute with empty data dict uses defaults."""
        result = await SampleTool_1_0.execute({
            "input": {"data": "test"},
            # general not provided - should use defaults
        })

        assert result.success is True
        assert result.output.result == "test x 1 (default)"

    @pytest.mark.asyncio
    async def test_execute_with_category_values(self) -> None:
        """Test execute uses category.value as keys."""
        # ExposureCategory.INPUT.value == "input"
        # ExposureCategory.GENERAL.value == "general"
        result = await SampleTool_1_0.execute({
            "input": {"data": "x"},
            "general": {"mode": "fast", "priority": 10},
        })

        assert result.success is True
        assert result.output.result == "x x 1 (fast)"


class TestToolResult:
    """Test ToolResult class."""

    def test_success_result(self) -> None:
        """Test successful result creation."""
        output = ToolOutput()
        result = ToolResult.success_result(output=output, duration_ms=10.5)

        assert result.success is True
        assert result.output == output
        assert result.error is None
        assert result.duration_ms == 10.5

    def test_failure_result(self) -> None:
        """Test failure result creation."""
        result = ToolResult.failure_result(
            error="Something went wrong",
            error_traceback="Traceback...",
            duration_ms=5.0,
        )

        assert result.success is False
        assert result.output is None
        assert result.error == "Something went wrong"
        assert result.error_traceback == "Traceback..."

    def test_result_metadata(self) -> None:
        """Test result with metadata."""
        result = ToolResult(
            success=True,
            output=ToolOutput(),
            metadata={"key": "value"},
        )

        assert result.metadata == {"key": "value"}
