"""
Tests for LocalEmbeddingManager cache directory handling and thread safety.

These tests verify that the cache directory is properly resolved
to an absolute path using the application's configured cache directory,
and that embedding initialization is thread-safe.
"""

import os
import tempfile
import threading
from pathlib import Path
from unittest.mock import MagicMock, patch


class TestLocalEmbeddingManagerCacheDir:
    """Tests for LocalEmbeddingManager cache directory configuration."""

    def test_cache_dir_uses_absolute_path_when_none(self):
        """When cache_dir is None, should use get_cache_directory()."""
        # Create a temporary directory to use as the cache directory
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            # Mock dependencies to avoid loading actual models
            with patch(
                "local_deep_research.web_search_engines.engines.search_engine_local.get_cache_directory",
                return_value=temp_path,
            ):
                # Also mock the embeddings to avoid loading real models
                with patch(
                    "local_deep_research.web_search_engines.engines.search_engine_local.LocalEmbeddingManager._load_indexed_folders",
                    return_value={},
                ):
                    from local_deep_research.web_search_engines.engines.search_engine_local import (
                        LocalEmbeddingManager,
                    )

                    manager = LocalEmbeddingManager(
                        embedding_model="test-model",
                        cache_dir=None,  # Should use get_cache_directory()
                    )

                    # Should resolve to temp_path / "local_search"
                    expected_path = temp_path / "local_search"
                    assert manager.cache_dir == expected_path
                    assert manager.cache_dir.is_absolute()

    def test_cache_dir_uses_explicit_path_when_provided(self):
        """When cache_dir is provided, should use that path."""
        with tempfile.TemporaryDirectory() as temp_dir:
            explicit_path = str(Path(temp_dir) / "my_custom_cache")

            with patch(
                "local_deep_research.web_search_engines.engines.search_engine_local.LocalEmbeddingManager._load_indexed_folders",
                return_value={},
            ):
                from local_deep_research.web_search_engines.engines.search_engine_local import (
                    LocalEmbeddingManager,
                )

                manager = LocalEmbeddingManager(
                    embedding_model="test-model",
                    cache_dir=explicit_path,
                )

                assert manager.cache_dir == Path(explicit_path)

    def test_cache_dir_not_relative(self):
        """Cache dir should never be a relative path like '.cache'."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            with patch(
                "local_deep_research.web_search_engines.engines.search_engine_local.get_cache_directory",
                return_value=temp_path,
            ):
                with patch(
                    "local_deep_research.web_search_engines.engines.search_engine_local.LocalEmbeddingManager._load_indexed_folders",
                    return_value={},
                ):
                    from local_deep_research.web_search_engines.engines.search_engine_local import (
                        LocalEmbeddingManager,
                    )

                    # Default behavior (cache_dir=None) should NOT result in .cache
                    manager = LocalEmbeddingManager(
                        embedding_model="test-model",
                    )

                    # The path should not start with ".cache"
                    assert not str(manager.cache_dir).startswith(".cache")
                    # The path should be absolute
                    assert manager.cache_dir.is_absolute()


class TestLocalSearchEngineCacheDir:
    """Tests for LocalSearchEngine cache directory configuration."""

    def test_from_config_uses_none_for_missing_cache_dir(self):
        """from_config should pass None when cache_dir not in config."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            with patch(
                "local_deep_research.web_search_engines.engines.search_engine_local.get_cache_directory",
                return_value=temp_path,
            ):
                with patch(
                    "local_deep_research.web_search_engines.engines.search_engine_local.LocalEmbeddingManager._load_indexed_folders",
                    return_value={},
                ):
                    from local_deep_research.web_search_engines.engines.search_engine_local import (
                        LocalSearchEngine,
                    )

                    # Create engine from config without cache_dir specified
                    config = {
                        "folder_paths": [
                            temp_dir
                        ],  # Use temp_dir as a valid folder
                    }

                    engine = LocalSearchEngine.from_config(config)

                    # The embedding manager's cache_dir should be absolute
                    assert engine.embedding_manager.cache_dir.is_absolute()
                    # Should not be the old relative path
                    assert not str(
                        engine.embedding_manager.cache_dir
                    ).startswith(".cache")


class TestEmbeddingThreadSafety:
    """Tests that embedding initialization is thread-safe."""

    def test_concurrent_embedding_access_initializes_once(self):
        """Multiple threads accessing .embeddings should only init once."""
        with tempfile.TemporaryDirectory() as temp_dir:
            temp_path = Path(temp_dir)

            with (
                patch(
                    "local_deep_research.web_search_engines.engines.search_engine_local.get_cache_directory",
                    return_value=temp_path,
                ),
                patch(
                    "local_deep_research.web_search_engines.engines.search_engine_local.LocalEmbeddingManager._load_indexed_folders",
                    return_value={},
                ),
            ):
                from local_deep_research.web_search_engines.engines.search_engine_local import (
                    LocalEmbeddingManager,
                )

                manager = LocalEmbeddingManager(
                    embedding_model="test-model",
                )

                # Track how many times _initialize_embeddings is called
                init_count = 0
                init_lock = threading.Lock()
                mock_embeddings = MagicMock()

                def counting_init():
                    nonlocal init_count
                    with init_lock:
                        init_count += 1
                    # Simulate slow initialization to widen race window
                    import time

                    time.sleep(0.1)
                    return mock_embeddings

                manager._initialize_embeddings = counting_init

                # Access embeddings from multiple threads concurrently
                results = []
                errors = []

                def access_embeddings():
                    try:
                        emb = manager.embeddings
                        results.append(emb)
                    except Exception as e:
                        errors.append(e)

                threads = [
                    threading.Thread(target=access_embeddings) for _ in range(4)
                ]
                for t in threads:
                    t.start()
                for t in threads:
                    t.join()

                assert not errors, f"Threads raised errors: {errors}"
                assert init_count == 1, (
                    f"_initialize_embeddings called {init_count} times, expected 1"
                )
                # All threads should get the same instance
                assert all(r is mock_embeddings for r in results)


class TestGetCacheDirectory:
    """Tests for get_cache_directory function."""

    def test_get_cache_directory_returns_absolute_path(self):
        """get_cache_directory should return an absolute path."""
        from local_deep_research.config.paths import get_cache_directory

        cache_dir = get_cache_directory()
        assert cache_dir.is_absolute()

    def test_get_cache_directory_respects_ldr_data_dir(self):
        """get_cache_directory should respect LDR_DATA_DIR env var."""
        with tempfile.TemporaryDirectory() as temp_dir:
            with patch.dict(os.environ, {"LDR_DATA_DIR": temp_dir}):
                from local_deep_research.config.paths import (
                    get_cache_directory,
                )

                cache_dir = get_cache_directory()

                # Should be under the LDR_DATA_DIR
                assert str(cache_dir).startswith(temp_dir)
                assert cache_dir.name == "cache"
