import unittest

from openai_resptools import ToolRegistry
from openai_resptools.errors import ToolNotFoundError
from openai_resptools.tool import ToolDef


class TestToolRegistry(unittest.TestCase):
    def test_init_empty(self):
        reg = ToolRegistry()
        self.assertEqual(len(reg), 0)
        self.assertEqual(reg.names(), [])

    def test_decorator_registration(self):
        reg = ToolRegistry()

        @reg.tool()
        def my_tool(x: int) -> int:
            """My tool description."""
            return x * 2

        self.assertIn("my_tool", reg)
        self.assertEqual(len(reg), 1)

        tool = reg.get("my_tool")
        self.assertEqual(tool.name, "my_tool")
        self.assertEqual(tool.description, "My tool description.")

        # Test call
        result = tool.call({"x": 21})
        self.assertEqual(result, 42)

    def test_decorator_custom_name(self):
        reg = ToolRegistry()

        @reg.tool(name="custom_tool", description="Custom desc")
        def my_func():
            pass

        self.assertIn("custom_tool", reg)
        self.assertNotIn("my_func", reg)

        tool = reg.get("custom_tool")
        self.assertEqual(tool.description, "Custom desc")

    def test_add_tool_def(self):
        reg = ToolRegistry()

        def func(a: int):
            return a

        tool_def = ToolDef.from_callable(func, name="manual_tool")
        reg.add(tool_def)

        self.assertIn("manual_tool", reg)

    def test_register_object(self):
        class MyService:
            def service_method(self, data: str) -> str:
                """Service method."""
                return f"Processed {data}"

            def _private(self):
                pass

        reg = ToolRegistry()
        service = MyService()
        reg.register_object(service)

        self.assertIn("service_method", reg)
        self.assertNotIn("_private", reg)

        tool = reg.get("service_method")
        self.assertEqual(tool.description, "Service method.")

    def test_registry_subclass(self):
        class MyRegistry(ToolRegistry):
            def my_tool(self, x: int) -> int:
                """Subclass tool."""
                return x + 1

        reg = MyRegistry()
        # Should register 'my_tool' but not ToolRegistry methods like 'add', 'get', etc.
        self.assertIn("my_tool", reg)
        self.assertNotIn("add", reg)
        self.assertNotIn("get", reg)

        # Verify it works
        result = reg.get("my_tool").call({"x": 10})
        self.assertEqual(result, 11)

    def test_get_errors(self):
        reg = ToolRegistry()
        with self.assertRaises(ToolNotFoundError):
            reg.get("missing")

        self.assertIsNone(reg.maybe_get("missing"))

    def test_overwrite_error(self):
        reg = ToolRegistry()

        @reg.tool(name="dupe")
        def func1():
            pass

        with self.assertRaises(ValueError):

            @reg.tool(name="dupe")
            def func2():
                pass

    def test_overwrite_allowed(self):
        reg = ToolRegistry()

        @reg.tool(name="dupe")
        def func1():
            return 1

        @reg.tool(name="dupe", overwrite=True)
        def func2():
            return 2

        self.assertEqual(reg.get("dupe").call({}), 2)

    def test_as_openai_tools(self):
        reg = ToolRegistry()

        @reg.tool()
        def simple(x: int):
            """Simple tool."""
            pass

        payload = reg.as_openai_tools()
        self.assertEqual(len(payload), 1)
        tool_schema = payload[0]
        self.assertEqual(tool_schema["type"], "function")
        self.assertEqual(tool_schema["name"], "simple")
        self.assertEqual(tool_schema["description"], "Simple tool.")
        self.assertIn("parameters", tool_schema)

    def test_allowed_tools_choice(self):
        reg = ToolRegistry()

        @reg.tool()
        def t1():
            pass

        @reg.tool()
        def t2():
            pass

        # Select all
        choice = reg.allowed_tools_choice()
        self.assertEqual(choice["type"], "allowed_tools")
        self.assertEqual(len(choice["tools"]), 2)

        # Select subset
        choice_sub = reg.allowed_tools_choice(["t1"])
        self.assertEqual(len(choice_sub["tools"]), 1)
        self.assertEqual(choice_sub["tools"][0]["name"], "t1")

        # Unknown tool error
        with self.assertRaises(ToolNotFoundError):
            reg.allowed_tools_choice(["t3"])


if __name__ == "__main__":
    unittest.main()
