"""Tests for async_operations module."""

import asyncio
from unittest.mock import MagicMock

import pytest

from openzim_mcp.async_operations import AsyncZimOperations
from openzim_mcp.config import OpenZimMcpConfig
from openzim_mcp.zim_operations import ZimOperations


class TestAsyncZimOperations:
    """Test AsyncZimOperations class."""

    @pytest.fixture
    def mock_zim_operations(self, test_config: OpenZimMcpConfig) -> MagicMock:
        """Create a mock ZimOperations instance."""
        mock = MagicMock(spec=ZimOperations)
        mock.list_zim_files.return_value = '{"files": []}'
        mock.list_zim_files_data.return_value = []
        mock.search_zim_file.return_value = '{"results": []}'
        mock.get_zim_entry.return_value = "Entry content"
        mock.get_zim_metadata.return_value = '{"title": "Test"}'
        mock.get_main_page.return_value = "Main page"
        mock.list_namespaces.return_value = '{"namespaces": []}'
        mock.browse_namespace.return_value = '{"entries": []}'
        mock.search_with_filters.return_value = '{"results": []}'
        mock.get_search_suggestions.return_value = '{"suggestions": []}'
        mock.get_article_structure.return_value = '{"structure": {}}'
        mock.extract_article_links.return_value = '{"links": []}'
        mock.get_entry_summary.return_value = '{"summary": "Test"}'
        mock.get_table_of_contents.return_value = '{"toc": []}'
        mock.get_binary_entry.return_value = '{"data": ""}'
        return mock

    @pytest.fixture
    def async_ops(self, mock_zim_operations: MagicMock) -> AsyncZimOperations:
        """Create AsyncZimOperations instance with mock."""
        return AsyncZimOperations(mock_zim_operations)

    def test_initialization(self, mock_zim_operations: MagicMock):
        """Test AsyncZimOperations initialization."""
        async_ops = AsyncZimOperations(mock_zim_operations)

        assert async_ops._ops == mock_zim_operations

    def test_sync_ops_property(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test sync_ops property returns underlying operations."""
        assert async_ops.sync_ops == mock_zim_operations

    @pytest.mark.asyncio
    async def test_list_zim_files(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async list_zim_files."""
        result = await async_ops.list_zim_files()

        assert result == '{"files": []}'
        mock_zim_operations.list_zim_files.assert_called_once()

    @pytest.mark.asyncio
    async def test_list_zim_files_data(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async list_zim_files_data."""
        result = await async_ops.list_zim_files_data()

        assert result == []
        mock_zim_operations.list_zim_files_data.assert_called_once()

    @pytest.mark.asyncio
    async def test_list_zim_files_passes_name_filter(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """name_filter is forwarded to the underlying sync op."""
        await async_ops.list_zim_files(name_filter="nginx")

        mock_zim_operations.list_zim_files.assert_called_once_with(name_filter="nginx")

    @pytest.mark.asyncio
    async def test_list_zim_files_data_passes_name_filter(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """name_filter is forwarded to the underlying sync op."""
        await async_ops.list_zim_files_data(name_filter="nginx")

        mock_zim_operations.list_zim_files_data.assert_called_once_with(
            name_filter="nginx"
        )

    @pytest.mark.asyncio
    async def test_search_zim_file(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async search_zim_file."""
        result = await async_ops.search_zim_file("/path/to/file.zim", "query", 10, 0)

        assert result == '{"results": []}'
        mock_zim_operations.search_zim_file.assert_called_once_with(
            "/path/to/file.zim", "query", 10, 0
        )

    @pytest.mark.asyncio
    async def test_get_zim_entry(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_zim_entry."""
        result = await async_ops.get_zim_entry("/path/to/file.zim", "A/Article", 5000)

        assert result == "Entry content"
        mock_zim_operations.get_zim_entry.assert_called_once_with(
            "/path/to/file.zim", "A/Article", 5000, 0
        )

    @pytest.mark.asyncio
    async def test_get_zim_metadata(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_zim_metadata."""
        result = await async_ops.get_zim_metadata("/path/to/file.zim")

        assert result == '{"title": "Test"}'
        mock_zim_operations.get_zim_metadata.assert_called_once_with(
            "/path/to/file.zim"
        )

    @pytest.mark.asyncio
    async def test_get_main_page(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_main_page."""
        result = await async_ops.get_main_page("/path/to/file.zim")

        assert result == "Main page"
        mock_zim_operations.get_main_page.assert_called_once_with("/path/to/file.zim")

    @pytest.mark.asyncio
    async def test_list_namespaces(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async list_namespaces."""
        result = await async_ops.list_namespaces("/path/to/file.zim")

        assert result == '{"namespaces": []}'
        mock_zim_operations.list_namespaces.assert_called_once_with("/path/to/file.zim")

    @pytest.mark.asyncio
    async def test_browse_namespace(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async browse_namespace."""
        result = await async_ops.browse_namespace("/path/to/file.zim", "A", 50, 0)

        assert result == '{"entries": []}'
        mock_zim_operations.browse_namespace.assert_called_once_with(
            "/path/to/file.zim", "A", 50, 0
        )

    @pytest.mark.asyncio
    async def test_search_with_filters(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async search_with_filters."""
        result = await async_ops.search_with_filters(
            "/path/to/file.zim", "query", "A", "text/html", 10, 0
        )

        assert result == '{"results": []}'
        mock_zim_operations.search_with_filters.assert_called_once_with(
            "/path/to/file.zim", "query", "A", "text/html", 10, 0
        )

    @pytest.mark.asyncio
    async def test_get_search_suggestions(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_search_suggestions."""
        result = await async_ops.get_search_suggestions(
            "/path/to/file.zim", "partial", 10
        )

        assert result == '{"suggestions": []}'
        mock_zim_operations.get_search_suggestions.assert_called_once_with(
            "/path/to/file.zim", "partial", 10
        )

    @pytest.mark.asyncio
    async def test_get_article_structure(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_article_structure."""
        result = await async_ops.get_article_structure("/path/to/file.zim", "C/Article")

        assert result == '{"structure": {}}'
        mock_zim_operations.get_article_structure.assert_called_once_with(
            "/path/to/file.zim", "C/Article"
        )

    @pytest.mark.asyncio
    async def test_extract_article_links(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async extract_article_links.

        v2 Phase B: ``kind`` defaults to ``"internal"`` (was ``None``).
        """
        result = await async_ops.extract_article_links("/path/to/file.zim", "C/Article")

        assert result == '{"links": []}'
        mock_zim_operations.extract_article_links.assert_called_once_with(
            "/path/to/file.zim", "C/Article", 100, 0, "internal"
        )

    @pytest.mark.asyncio
    async def test_get_entry_summary(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_entry_summary."""
        result = await async_ops.get_entry_summary(
            "/path/to/file.zim", "C/Article", 200
        )

        assert result == '{"summary": "Test"}'
        mock_zim_operations.get_entry_summary.assert_called_once_with(
            "/path/to/file.zim", "C/Article", 200
        )

    @pytest.mark.asyncio
    async def test_get_table_of_contents(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_table_of_contents."""
        result = await async_ops.get_table_of_contents("/path/to/file.zim", "C/Article")

        assert result == '{"toc": []}'
        mock_zim_operations.get_table_of_contents.assert_called_once_with(
            "/path/to/file.zim", "C/Article"
        )

    @pytest.mark.asyncio
    async def test_get_binary_entry(
        self, async_ops: AsyncZimOperations, mock_zim_operations: MagicMock
    ):
        """Test async get_binary_entry."""
        result = await async_ops.get_binary_entry(
            "/path/to/file.zim", "I/image.png", 1000000, True
        )

        assert result == '{"data": ""}'
        mock_zim_operations.get_binary_entry.assert_called_once_with(
            "/path/to/file.zim", "I/image.png", 1000000, True
        )


class TestAsyncOperationsThreading:
    """Test that async operations properly use threading."""

    @pytest.mark.asyncio
    async def test_operations_run_in_thread(self):
        """Test that operations run in a separate thread."""
        import threading

        operation_thread_id = None

        def sync_operation():
            nonlocal operation_thread_id
            operation_thread_id = threading.current_thread().ident
            return "result"

        # Simulate running in thread
        result = await asyncio.to_thread(sync_operation)

        assert result == "result"
        # When using asyncio.to_thread, the operation runs in a different thread
        # Note: In some test environments, this might be the same thread
        # The important thing is that to_thread was used


class TestAsyncOperationsIntegration:
    """Integration tests for AsyncZimOperations."""

    @pytest.fixture
    def real_async_ops(self, test_config: OpenZimMcpConfig) -> AsyncZimOperations:
        """Create real AsyncZimOperations with real ZimOperations."""
        from openzim_mcp.cache import OpenZimMcpCache
        from openzim_mcp.content_processor import ContentProcessor
        from openzim_mcp.security import PathValidator

        cache = OpenZimMcpCache(test_config.cache)
        content_processor = ContentProcessor(test_config.content.snippet_length)
        path_validator = PathValidator(test_config.allowed_directories)

        zim_ops = ZimOperations(
            config=test_config,
            path_validator=path_validator,
            cache=cache,
            content_processor=content_processor,
        )

        return AsyncZimOperations(zim_ops)

    @pytest.mark.asyncio
    async def test_list_zim_files_integration(self, real_async_ops: AsyncZimOperations):
        """Test list_zim_files with real operations."""
        # This will return empty or error since no real ZIM files
        result = await real_async_ops.list_zim_files()

        # Should return valid string result
        # May return empty list or error message
        assert isinstance(result, str)
