"""Unit tests for resolver utilities using unittest.

## `resolve`

*   Successful resolution with synchronous resolvers.
*   Handling non-callable resolver raising `TypeError`.
*   Handling resolvers returning `None` raising `LookupError`.
*   Correctly wrapping exceptions from sync resolvers in `ResolverFailedError`.
*   Handling missing resolvers raising `ValueError`.

## `resolve_*` functions

*   Successful resolution to the correct type via the core `resolve` function.
*   Correct propagation of errors (e.g., `ResolverFailedError`, `LookupError`)
    from the core `resolve` function.
"""

import unittest
from typing import Any

from dotpromptz.resolvers import ResolverFailedError, resolve, resolve_json_schema, resolve_partial, resolve_tool
from dotpromptz.typing import JsonSchema, ToolDefinition


class MockSyncResolver:
    """Mock sync resolver callable."""

    def __init__(self, data: dict[str, Any], error: Exception | None = None) -> None:
        """Initialize the mock sync resolver."""
        self._data = data
        self._error = error

    def __call__(self, name: str) -> Any:
        """Mock sync resolver callable."""
        if self._error:
            raise self._error
        return self._data.get(name)


mock_tool_def = ToolDefinition(name='test_tool', inputSchema={})
mock_partial_content = 'This is a partial.'
mock_json_schema: JsonSchema = {'type': 'string', 'description': 'A test schema'}


class TestResolve(unittest.TestCase):
    """Tests for resolver functions."""

    def test_resolve_resolver_none(self) -> None:
        """Test ValueError when resolver is None."""
        with self.assertRaisesRegex(ValueError, 'test resolver is not defined'):
            resolve('obj', 'test', None)

    def test_resolve_sync_success(self) -> None:
        """Test successful resolution with a sync resolver."""
        resolver = MockSyncResolver({'obj1': 'value1'})
        result: Any = resolve('obj1', 'test', resolver)
        self.assertEqual(result, 'value1')

    def test_resolve_resolver_not_callable(self) -> None:
        """Test TypeError when resolver is not callable."""
        with self.assertRaisesRegex(TypeError, "test resolver for 'obj' is not callable"):
            resolve('obj', 'test', 'not_a_callable')  # type: ignore

    def test_resolve_resolver_returns_none(self) -> None:
        """Test LookupError when resolver returns None."""
        resolver_sync = MockSyncResolver({})
        with self.assertRaisesRegex(LookupError, "test resolver for 'not_found' returned None"):
            resolve('not_found', 'test', resolver_sync)

    def test_resolve_sync_resolver_raises_error(self) -> None:
        """Test ResolverFailedError when sync resolver raises an error."""
        original_error = ValueError('Sync resolver error')
        resolver = MockSyncResolver({}, error=original_error)
        with self.assertRaisesRegex(ResolverFailedError, r'test resolver failed for obj; Sync resolver error') as cm:
            resolve('obj', 'test', resolver)
        self.assertIs(cm.exception.__cause__, original_error)


class TestResolveTool(unittest.TestCase):
    """Tests for tool resolver functions."""

    def test_resolve_tool_success(self) -> None:
        """Test successful tool resolution."""
        resolver = MockSyncResolver({'my_tool': mock_tool_def})
        result = resolve_tool('my_tool', resolver)
        self.assertEqual(result, mock_tool_def)

    def test_resolve_tool_fails(self) -> None:
        """Test failing tool resolution propagates error."""
        resolver = MockSyncResolver({}, error=ValueError('Tool fail'))
        with self.assertRaisesRegex(ResolverFailedError, r'tool resolver failed for bad_tool; Tool fail'):
            resolve_tool('bad_tool', resolver)


class TestResolvePartial(unittest.TestCase):
    """Tests for partial resolver functions."""

    def test_resolve_partial_success(self) -> None:
        """Test successful partial resolution."""
        resolver = MockSyncResolver({'my_partial': mock_partial_content})
        result = resolve_partial('my_partial', resolver)
        self.assertEqual(result, mock_partial_content)

    def test_resolve_partial_fails(self) -> None:
        """Test failing partial resolution propagates error."""
        with self.assertRaisesRegex(LookupError, "partial resolver for 'missing_partial' returned None"):
            resolve_partial('missing_partial', MockSyncResolver({}))


class TestResolveJsonSchema(unittest.TestCase):
    """Tests for JSON schema resolver function."""

    def test_resolve_json_schema_success_sync(self) -> None:
        """Test successful schema resolution with sync resolver."""
        resolver = MockSyncResolver({'MySchema': mock_json_schema})
        result = resolve_json_schema('MySchema', resolver)
        self.assertEqual(result, mock_json_schema)

    def test_resolve_json_schema_fails_error(self) -> None:
        """Test failing schema resolution propagates error."""
        resolver = MockSyncResolver({}, error=TypeError('Schema Error'))
        with self.assertRaisesRegex(ResolverFailedError, r'schema resolver failed for bad_schema; Schema Error'):
            resolve_json_schema('bad_schema', resolver)

    def test_resolve_json_schema_fails_none(self) -> None:
        """Test failing schema resolution propagates error when None is returned."""
        resolver = MockSyncResolver({})
        with self.assertRaisesRegex(LookupError, r"schema resolver for 'missing_schema' returned None"):
            resolve_json_schema('missing_schema', resolver)


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