"""
Extended Tools Tests

Comprehensive tests for all extended tool categories:
- DatabaseTools (6 tools)
- NotificationTools (5 tools)
- CacheTools (9 tools)
- APITools (6 tools)
- FileTools (7 tools)
"""

import asyncio
import json
import os
import tempfile
from pathlib import Path
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from bgf_agents.tools import (
    DatabaseTools,
    NotificationTools,
    CacheTools,
    APITools,
    FileTools,
    DATABASE_TOOLS,
    NOTIFICATION_TOOLS,
    CACHE_TOOLS,
    API_TOOLS,
    FILE_TOOLS,
)


# =============================================================================
# DatabaseTools Tests (6 tools)
# =============================================================================

class TestDatabaseTools:
    """Tests for DatabaseTools"""

    def test_initialization(self):
        """Test DatabaseTools initialization"""
        db = DatabaseTools(db_url="postgresql://localhost:5432/test")
        assert db.db_url == "postgresql://localhost:5432/test"
        assert db._pool is None

    def test_initialization_from_env(self):
        """Test DatabaseTools initialization from environment"""
        with patch.dict(os.environ, {"TIMESCALEDB_URL": "postgresql://env:5432/db"}):
            db = DatabaseTools()
            assert db.db_url == "postgresql://env:5432/db"

    @pytest.mark.asyncio
    async def test_execute_query_success(self):
        """Test execute_query with mocked pool"""
        db = DatabaseTools()

        # Asyncpg Record objects can be converted to dict via dict(record)
        # The dict() call iterates over items, so we need to support that
        class MockRecord(dict):
            """Mock asyncpg Record that supports dict conversion"""
            pass

        # Mock the connection pool
        mock_conn = AsyncMock()
        mock_conn.fetch = AsyncMock(return_value=[
            MockRecord({"id": 1, "name": "test"}),
            MockRecord({"id": 2, "name": "test2"}),
        ])
        mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
        mock_conn.__aexit__ = AsyncMock(return_value=None)

        mock_pool = MagicMock()
        mock_pool.acquire = MagicMock(return_value=mock_conn)
        db._pool = mock_pool

        result = await db.execute_query("SELECT * FROM test")

        assert result["success"] is True
        assert result["count"] == 2
        assert len(result["rows"]) == 2

    @pytest.mark.asyncio
    async def test_execute_query_no_pool(self):
        """Test execute_query when pool not initialized"""
        db = DatabaseTools()
        db._pool = None

        # Mock initialize to fail silently
        with patch.object(db, 'initialize', new_callable=AsyncMock):
            result = await db.execute_query("SELECT 1")
            assert result.get("error") == "Database connection not available"

    @pytest.mark.asyncio
    async def test_get_table_info(self):
        """Test get_table_info"""
        db = DatabaseTools()

        # Create mock Record objects
        class MockRecord:
            def __init__(self, data):
                self._data = data
            def __iter__(self):
                return iter(self._data.items())

        mock_conn = AsyncMock()
        mock_conn.fetch = AsyncMock(return_value=[
            MockRecord({"column_name": "id", "data_type": "integer", "is_nullable": "NO"}),
            MockRecord({"column_name": "name", "data_type": "varchar", "is_nullable": "YES"}),
        ])
        mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
        mock_conn.__aexit__ = AsyncMock(return_value=None)

        mock_pool = MagicMock()
        mock_pool.acquire = MagicMock(return_value=mock_conn)
        db._pool = mock_pool

        result = await db.get_table_info("users")

        assert result["table_name"] == "users"  # Fixed: was table
        assert result["column_count"] == 2  # Fixed: was len(columns)
        assert len(result["columns"]) == 2

    @pytest.mark.asyncio
    async def test_list_tables(self):
        """Test list_tables"""
        db = DatabaseTools()

        # Create mock Record objects that support __getitem__
        class MockRecord:
            def __init__(self, data):
                self._data = data
            def __iter__(self):
                return iter(self._data.items())
            def __getitem__(self, key):
                return self._data[key]

        mock_conn = AsyncMock()
        mock_conn.fetch = AsyncMock(return_value=[
            MockRecord({"table_name": "users"}),
            MockRecord({"table_name": "orders"}),
        ])
        mock_conn.__aenter__ = AsyncMock(return_value=mock_conn)
        mock_conn.__aexit__ = AsyncMock(return_value=None)

        mock_pool = MagicMock()
        mock_pool.acquire = MagicMock(return_value=mock_conn)
        db._pool = mock_pool

        result = await db.list_tables()

        assert result["count"] == 2  # Fixed: was success
        assert len(result["tables"]) == 2

    def test_tool_definitions(self):
        """Test DATABASE_TOOLS definitions"""
        assert len(DATABASE_TOOLS) == 6

        tool_names = [t["name"] for t in DATABASE_TOOLS]
        assert "execute_query" in tool_names
        assert "get_table_info" in tool_names
        assert "get_table_stats" in tool_names
        assert "list_tables" in tool_names
        assert "sample_data" in tool_names
        assert "get_column_stats" in tool_names


# =============================================================================
# NotificationTools Tests (5 tools)
# =============================================================================

class TestNotificationTools:
    """Tests for NotificationTools"""

    def test_initialization(self):
        """Test NotificationTools initialization"""
        notif = NotificationTools(
            slack_webhook_url="https://hooks.slack.com/test",
            pagerduty_key="test-key"
        )
        assert notif.slack_webhook_url == "https://hooks.slack.com/test"
        assert notif.pagerduty_key == "test-key"

    def test_initialization_from_env(self):
        """Test NotificationTools initialization from environment"""
        with patch.dict(os.environ, {
            "SLACK_WEBHOOK_URL": "https://hooks.slack.com/env",
            "PAGERDUTY_ROUTING_KEY": "env-key"
        }):
            notif = NotificationTools()
            assert notif.slack_webhook_url == "https://hooks.slack.com/env"
            assert notif.pagerduty_key == "env-key"

    @pytest.mark.asyncio
    async def test_send_slack_success(self):
        """Test send_slack success"""
        notif = NotificationTools(slack_webhook_url="https://hooks.slack.com/test")

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.raise_for_status = MagicMock()

            mock_instance = AsyncMock()
            mock_instance.post = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await notif.send_slack("Test message", severity="warning")

            assert result["success"] is True
            assert result["channel"] == "slack"

    @pytest.mark.asyncio
    async def test_send_slack_no_webhook(self):
        """Test send_slack without webhook configured"""
        notif = NotificationTools()
        notif.slack_webhook_url = None

        result = await notif.send_slack("Test message")

        assert result["success"] is False
        assert "not configured" in result["error"]

    @pytest.mark.asyncio
    async def test_send_webhook_success(self):
        """Test send_webhook success"""
        notif = NotificationTools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.status_code = 200
            mock_response.text = "OK"

            mock_instance = AsyncMock()
            mock_instance.post = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await notif.send_webhook(
                "https://example.com/webhook",
                {"event": "test"}
            )

            assert result["success"] is True
            assert result["status_code"] == 200

    @pytest.mark.asyncio
    async def test_send_pagerduty_no_key(self):
        """Test send_pagerduty without key configured"""
        notif = NotificationTools()
        notif.pagerduty_key = None

        result = await notif.send_pagerduty("Test alert")

        assert result["success"] is False
        assert "not configured" in result["error"]

    def test_get_notification_history(self):
        """Test get_notification_history"""
        notif = NotificationTools()

        # Initially empty
        history = notif.get_notification_history()
        assert len(history) == 0

    def test_tool_definitions(self):
        """Test NOTIFICATION_TOOLS definitions"""
        assert len(NOTIFICATION_TOOLS) == 5

        tool_names = [t["name"] for t in NOTIFICATION_TOOLS]
        assert "send_slack" in tool_names
        assert "send_webhook" in tool_names
        assert "send_pagerduty" in tool_names
        assert "resolve_pagerduty" in tool_names
        assert "get_notification_history" in tool_names


# =============================================================================
# CacheTools Tests (9 tools)
# =============================================================================

class TestCacheTools:
    """Tests for CacheTools"""

    def test_initialization(self):
        """Test CacheTools initialization"""
        cache = CacheTools(redis_url="redis://localhost:6379")
        assert cache.redis_url == "redis://localhost:6379"
        assert cache._redis is None

    def test_initialization_from_env(self):
        """Test CacheTools initialization from environment"""
        with patch.dict(os.environ, {"REDIS_URL": "redis://env:6379"}):
            cache = CacheTools()
            assert cache.redis_url == "redis://env:6379"

    @pytest.mark.asyncio
    async def test_get_cache_hit(self):
        """Test cache get with hit"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.get = AsyncMock(return_value=b'{"key": "value"}')
        mock_redis.ttl = AsyncMock(return_value=3600)
        cache._redis = mock_redis

        result = await cache.get("test_key")

        assert result["exists"] is True
        assert result["value"] == {"key": "value"}
        assert result["ttl_seconds"] == 3600

    @pytest.mark.asyncio
    async def test_get_cache_miss(self):
        """Test cache get with miss"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.get = AsyncMock(return_value=None)
        mock_redis.ttl = AsyncMock(return_value=-2)
        cache._redis = mock_redis

        result = await cache.get("nonexistent_key")

        assert result["exists"] is False
        assert result["value"] is None

    @pytest.mark.asyncio
    async def test_set_with_ttl(self):
        """Test cache set with TTL"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.setex = AsyncMock(return_value=True)
        cache._redis = mock_redis

        result = await cache.set("test_key", {"data": "value"}, ttl_seconds=3600)

        assert result["success"] is True
        assert result["ttl_seconds"] == 3600
        mock_redis.setex.assert_called_once()

    @pytest.mark.asyncio
    async def test_set_without_ttl(self):
        """Test cache set without TTL"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.set = AsyncMock(return_value=True)
        cache._redis = mock_redis

        result = await cache.set("test_key", "simple_value")

        assert result["success"] is True
        mock_redis.set.assert_called_once()

    @pytest.mark.asyncio
    async def test_delete(self):
        """Test cache delete"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.delete = AsyncMock(return_value=1)
        cache._redis = mock_redis

        result = await cache.delete("test_key")

        assert result["success"] is True
        assert result["deleted"] is True

    @pytest.mark.asyncio
    async def test_keys_pattern(self):
        """Test cache keys with pattern"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.keys = AsyncMock(return_value=[b"user:1", b"user:2", b"user:3"])
        cache._redis = mock_redis

        result = await cache.keys("user:*")

        assert result["pattern"] == "user:*"
        assert result["count"] == 3
        assert "user:1" in result["keys"]

    @pytest.mark.asyncio
    async def test_stream_add(self):
        """Test Redis stream add"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.xadd = AsyncMock(return_value=b"1234567890-0")
        cache._redis = mock_redis

        result = await cache.stream_add("test_stream", {"event": "test"})

        assert result["success"] is True
        assert result["stream"] == "test_stream"
        assert result["message_id"] == "1234567890-0"

    @pytest.mark.asyncio
    async def test_stream_read(self):
        """Test Redis stream read"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.xread = AsyncMock(return_value=[
            (b"test_stream", [
                (b"1234567890-0", {b"event": b"test"}),
            ])
        ])
        cache._redis = mock_redis

        result = await cache.stream_read("test_stream", count=10)

        assert result["stream"] == "test_stream"
        assert result["count"] == 1

    @pytest.mark.asyncio
    async def test_stream_len(self):
        """Test Redis stream length"""
        cache = CacheTools()

        mock_redis = AsyncMock()
        mock_redis.xlen = AsyncMock(return_value=42)
        cache._redis = mock_redis

        result = await cache.stream_len("test_stream")

        assert result["stream"] == "test_stream"
        assert result["length"] == 42

    def test_tool_definitions(self):
        """Test CACHE_TOOLS definitions"""
        assert len(CACHE_TOOLS) == 9

        tool_names = [t["name"] for t in CACHE_TOOLS]
        assert "cache_get" in tool_names
        assert "cache_set" in tool_names
        assert "cache_delete" in tool_names
        assert "cache_keys" in tool_names
        assert "cache_flush_pattern" in tool_names
        assert "cache_info" in tool_names
        assert "stream_add" in tool_names
        assert "stream_read" in tool_names
        assert "stream_len" in tool_names


# =============================================================================
# APITools Tests (6 tools)
# =============================================================================

class TestAPITools:
    """Tests for APITools"""

    def test_initialization(self):
        """Test APITools initialization"""
        api = APITools(default_timeout=60.0, max_retries=5)
        assert api.default_timeout == 60.0
        assert api.max_retries == 5

    def test_initialization_defaults(self):
        """Test APITools default initialization"""
        api = APITools()
        assert api.default_timeout == 30.0
        assert api.max_retries == 3

    @pytest.mark.asyncio
    async def test_get_success(self):
        """Test GET request success"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.is_success = True
            mock_response.status_code = 200
            mock_response.json = MagicMock(return_value={"data": "test"})

            mock_instance = AsyncMock()
            mock_instance.get = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.get("https://api.example.com/data")

            assert result["success"] is True
            assert result["status_code"] == 200
            assert result["data"] == {"data": "test"}

    @pytest.mark.asyncio
    async def test_get_failure(self):
        """Test GET request failure"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get = AsyncMock(side_effect=Exception("Connection failed"))
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.get("https://api.example.com/data")

            assert result["success"] is False
            assert "Connection failed" in result["error"]

    @pytest.mark.asyncio
    async def test_post_json_body(self):
        """Test POST request with JSON body"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.is_success = True
            mock_response.status_code = 201
            mock_response.json = MagicMock(return_value={"id": 1})

            mock_instance = AsyncMock()
            mock_instance.post = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.post(
                "https://api.example.com/users",
                json_body={"name": "test"}
            )

            assert result["success"] is True
            assert result["status_code"] == 201

    @pytest.mark.asyncio
    async def test_graphql_query(self):
        """Test GraphQL query"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.is_success = True
            mock_response.status_code = 200
            mock_response.json = MagicMock(return_value={
                "data": {"user": {"name": "test"}}
            })

            mock_instance = AsyncMock()
            mock_instance.post = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.graphql(
                "https://api.example.com/graphql",
                query="query { user { name } }"
            )

            assert result["success"] is True
            assert result["data"]["data"]["user"]["name"] == "test"

    @pytest.mark.asyncio
    async def test_health_check_healthy(self):
        """Test health check - healthy"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.is_success = True
            mock_response.status_code = 200
            mock_response.json = MagicMock(return_value={"status": "ok"})

            mock_instance = AsyncMock()
            mock_instance.get = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.health_check("https://api.example.com/health")

            assert result["healthy"] is True
            assert result["status_code"] == 200

    @pytest.mark.asyncio
    async def test_health_check_unhealthy(self):
        """Test health check - unhealthy"""
        api = APITools()

        with patch("httpx.AsyncClient") as mock_client:
            mock_response = MagicMock()
            mock_response.is_success = False
            mock_response.status_code = 503
            mock_response.json = MagicMock(return_value={"status": "error"})

            mock_instance = AsyncMock()
            mock_instance.get = AsyncMock(return_value=mock_response)
            mock_instance.__aenter__ = AsyncMock(return_value=mock_instance)
            mock_instance.__aexit__ = AsyncMock(return_value=None)
            mock_client.return_value = mock_instance

            result = await api.health_check("https://api.example.com/health")

            assert result["healthy"] is False
            assert result["status_code"] == 503

    @pytest.mark.asyncio
    async def test_batch_health_check(self):
        """Test batch health check"""
        api = APITools()

        with patch.object(api, 'health_check', new_callable=AsyncMock) as mock_health:
            mock_health.side_effect = [
                {"url": "https://a.com", "healthy": True, "status_code": 200},
                {"url": "https://b.com", "healthy": False, "status_code": 503},
                {"url": "https://c.com", "healthy": True, "status_code": 200},
            ]

            result = await api.batch_health_check([
                "https://a.com",
                "https://b.com",
                "https://c.com",
            ])

            assert result["total"] == 3
            assert result["healthy"] == 2
            assert result["unhealthy"] == 1

    def test_get_call_stats_empty(self):
        """Test get_call_stats when empty"""
        api = APITools()

        stats = api.get_call_stats()

        assert stats["total_calls"] == 0
        assert stats["success_rate"] == 0.0

    def test_tool_definitions(self):
        """Test API_TOOLS definitions"""
        assert len(API_TOOLS) == 6

        tool_names = [t["name"] for t in API_TOOLS]
        assert "api_get" in tool_names
        assert "api_post" in tool_names
        assert "api_graphql" in tool_names
        assert "api_health_check" in tool_names
        assert "api_batch_health_check" in tool_names
        assert "api_call_stats" in tool_names


# =============================================================================
# FileTools Tests (7 tools)
# =============================================================================

class TestFileTools:
    """Tests for FileTools"""

    def test_initialization(self):
        """Test FileTools initialization"""
        files = FileTools(base_path="/tmp/test")
        assert files.base_path == Path("/tmp/test")

    def test_initialization_default(self):
        """Test FileTools default initialization"""
        files = FileTools()
        assert files.base_path == Path.cwd()

    def test_resolve_path_valid(self):
        """Test path resolution for valid path"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Resolve tmpdir to handle macOS symlinks (/var -> /private/var)
            resolved_tmpdir = str(Path(tmpdir).resolve())
            files = FileTools(base_path=tmpdir)
            resolved = files._resolve_path("subdir/file.txt")
            assert str(resolved).startswith(resolved_tmpdir)

    def test_resolve_path_traversal_blocked(self):
        """Test path traversal is blocked"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)
            with pytest.raises(ValueError, match="outside base directory"):
                files._resolve_path("../../../etc/passwd")

    def test_read_file_success(self):
        """Test read_file success"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create test file
            test_file = Path(tmpdir) / "test.txt"
            test_file.write_text("Hello, World!")

            result = files.read_file("test.txt")

            assert result["success"] is True
            assert result["content"] == "Hello, World!"
            assert result["lines"] == 1

    def test_read_file_not_found(self):
        """Test read_file file not found"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            result = files.read_file("nonexistent.txt")

            assert result["success"] is False
            assert "not found" in result["error"]

    def test_read_file_too_large(self):
        """Test read_file file too large"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create large file (simulate with max_size_mb=0.0001)
            test_file = Path(tmpdir) / "large.txt"
            test_file.write_text("x" * 1000)

            result = files.read_file("large.txt", max_size_mb=0.0001)

            assert result["success"] is False
            assert "too large" in result["error"]

    def test_write_file_success(self):
        """Test write_file success"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            result = files.write_file("new_file.txt", "New content")

            assert result["success"] is True

            # Verify file was created
            content = (Path(tmpdir) / "new_file.txt").read_text()
            assert content == "New content"

    def test_write_file_create_dirs(self):
        """Test write_file creates parent directories"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            result = files.write_file("subdir/deep/file.txt", "Content")

            assert result["success"] is True
            assert (Path(tmpdir) / "subdir" / "deep" / "file.txt").exists()

    def test_append_file(self):
        """Test append_file"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create initial file
            test_file = Path(tmpdir) / "append.txt"
            test_file.write_text("Line 1\n")

            result = files.append_file("append.txt", "Line 2\n")

            assert result["success"] is True

            content = test_file.read_text()
            assert content == "Line 1\nLine 2\n"

    def test_list_directory(self):
        """Test list_directory"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Resolve to handle macOS symlinks (/var -> /private/var)
            resolved_tmpdir = str(Path(tmpdir).resolve())
            files = FileTools(base_path=resolved_tmpdir)

            # Create test files
            (Path(resolved_tmpdir) / "file1.txt").write_text("a")
            (Path(resolved_tmpdir) / "file2.txt").write_text("b")
            (Path(resolved_tmpdir) / "subdir").mkdir()

            result = files.list_directory(".")

            assert result["success"] is True
            assert result["count"] == 3

    def test_list_directory_with_pattern(self):
        """Test list_directory with pattern"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Resolve to handle macOS symlinks
            resolved_tmpdir = str(Path(tmpdir).resolve())
            files = FileTools(base_path=resolved_tmpdir)

            # Create test files
            (Path(resolved_tmpdir) / "file1.txt").write_text("a")
            (Path(resolved_tmpdir) / "file2.py").write_text("b")
            (Path(resolved_tmpdir) / "file3.txt").write_text("c")

            result = files.list_directory(".", pattern="*.txt")

            assert result["success"] is True
            assert result["count"] == 2

    def test_file_info(self):
        """Test file_info"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create test file
            test_file = Path(tmpdir) / "info.txt"
            test_file.write_text("Test content")

            result = files.file_info("info.txt")

            assert result["success"] is True
            assert result["name"] == "info.txt"
            assert result["type"] == "file"
            assert result["extension"] == ".txt"

    def test_search_files(self):
        """Test search_files"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Resolve to handle macOS symlinks
            resolved_tmpdir = str(Path(tmpdir).resolve())
            files = FileTools(base_path=resolved_tmpdir)

            # Create test files
            (Path(resolved_tmpdir) / "file1.py").write_text("def hello():")
            (Path(resolved_tmpdir) / "file2.py").write_text("def world():")
            (Path(resolved_tmpdir) / "readme.txt").write_text("README")

            result = files.search_files("*.py")

            assert result["success"] is True
            assert result["count"] == 2

    def test_search_files_with_content(self):
        """Test search_files with content pattern"""
        with tempfile.TemporaryDirectory() as tmpdir:
            # Resolve to handle macOS symlinks
            resolved_tmpdir = str(Path(tmpdir).resolve())
            files = FileTools(base_path=resolved_tmpdir)

            # Create test files
            (Path(resolved_tmpdir) / "file1.py").write_text("def hello(): pass")
            (Path(resolved_tmpdir) / "file2.py").write_text("def world(): pass")

            result = files.search_files("*.py", content_pattern="hello")

            assert result["success"] is True
            assert result["count"] == 1
            assert result["results"][0]["content_matches"] == 1

    def test_delete_file(self):
        """Test delete_file"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create test file
            test_file = Path(tmpdir) / "delete_me.txt"
            test_file.write_text("Delete this")

            result = files.delete_file("delete_me.txt")

            assert result["success"] is True
            assert not test_file.exists()

    def test_delete_file_not_found(self):
        """Test delete_file file not found"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            result = files.delete_file("nonexistent.txt")

            assert result["success"] is False
            assert "not found" in result["error"]

    def test_read_json(self):
        """Test read_json"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create test JSON file
            test_file = Path(tmpdir) / "data.json"
            test_file.write_text('{"key": "value", "number": 42}')

            result = files.read_json("data.json")

            assert result["success"] is True
            assert result["data"]["key"] == "value"
            assert result["data"]["number"] == 42

    def test_read_json_invalid(self):
        """Test read_json with invalid JSON"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            # Create invalid JSON file
            test_file = Path(tmpdir) / "invalid.json"
            test_file.write_text("not valid json {")

            result = files.read_json("invalid.json")

            assert result["success"] is False
            assert "Invalid JSON" in result["error"]

    def test_write_json(self):
        """Test write_json"""
        with tempfile.TemporaryDirectory() as tmpdir:
            files = FileTools(base_path=tmpdir)

            data = {"key": "value", "list": [1, 2, 3]}
            result = files.write_json("output.json", data)

            assert result["success"] is True

            # Verify content
            content = json.loads((Path(tmpdir) / "output.json").read_text())
            assert content == data

    def test_tool_definitions(self):
        """Test FILE_TOOLS definitions"""
        assert len(FILE_TOOLS) == 7

        tool_names = [t["name"] for t in FILE_TOOLS]
        assert "read_file" in tool_names
        assert "write_file" in tool_names
        assert "list_directory" in tool_names
        assert "file_info" in tool_names
        assert "search_files" in tool_names
        assert "read_json" in tool_names
        assert "write_json" in tool_names


# =============================================================================
# Integration Tests
# =============================================================================

class TestExtendedToolsIntegration:
    """Integration tests for extended tools"""

    def test_all_tools_exported(self):
        """Test all tools are properly exported"""
        from bgf_agents.tools import ALL_TOOLS_EXTENDED

        assert len(ALL_TOOLS_EXTENDED) == 76

        # Check each tool has required fields
        for tool in ALL_TOOLS_EXTENDED:
            assert "name" in tool
            assert "description" in tool
            assert "input_schema" in tool

    def test_tool_names_unique(self):
        """Test all tool names are unique"""
        from bgf_agents.tools import ALL_TOOLS_EXTENDED

        names = [t["name"] for t in ALL_TOOLS_EXTENDED]
        assert len(names) == len(set(names)), "Duplicate tool names found"

    def test_tool_schemas_valid(self):
        """Test all tool schemas are valid JSON Schema"""
        from bgf_agents.tools import ALL_TOOLS_EXTENDED

        for tool in ALL_TOOLS_EXTENDED:
            schema = tool["input_schema"]
            assert "type" in schema
            assert schema["type"] == "object"

            if "required" in schema:
                assert isinstance(schema["required"], list)

                # Check required fields exist in properties
                if "properties" in schema:
                    for req in schema["required"]:
                        assert req in schema["properties"], f"Required field {req} not in properties for {tool['name']}"


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
