import os
import sys
from pathlib import Path
from unittest.mock import Mock

import pytest

# Handle import paths for testing
sys.path.append(str(Path(__file__).parent.parent))

# Skip all tests in this module when using fallback LLM
pytestmark = pytest.mark.skipif(
    os.environ.get("LDR_USE_FALLBACK_LLM", "") == "true",
    reason="These tests require real LLM providers, not fallback",
)


@pytest.fixture
def mock_llm():
    """Create a mock LLM for testing."""
    mock = Mock()
    mock.invoke.return_value = Mock(content="Mocked LLM response")
    return mock


@pytest.fixture
def mock_search():
    """Create a mock search engine for testing."""
    mock = Mock()
    mock.run.return_value = [
        {
            "title": "Mocked Search Result",
            "link": "https://example.com/mocked",
            "snippet": "This is a mocked search result snippet.",
        }
    ]
    return mock


@pytest.fixture
def mock_strategy():
    """Create a mock search strategy for testing."""
    mock = Mock()
    mock.analyze_topic.return_value = {
        "findings": [{"content": "Test finding"}],
        "current_knowledge": "Test knowledge summary",
        "iterations": 1,
        "questions_by_iteration": {1: ["Question 1?", "Question 2?"]},
    }
    return mock


def test_progress_callback_forwarding(monkeypatch, mock_search, mock_llm):
    """Test that progress callbacks are properly forwarded to the strategy."""
    # Create a mock strategy class and instance
    mock_strategy_class = Mock()
    mock_strategy_instance = Mock()
    mock_strategy_class.return_value = mock_strategy_instance

    # Mock SourceBasedSearchStrategy which is the default
    monkeypatch.setattr(
        "local_deep_research.search_system.SourceBasedSearchStrategy",
        mock_strategy_class,
    )
    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create the search system (will use default source-based strategy)
    system = AdvancedSearchSystem(llm=mock_llm, search=mock_search)

    # Create a mock progress callback
    mock_callback = Mock()

    # Set the callback
    system.set_progress_callback(mock_callback)

    # Internal _progress_callback should call the user-provided callback
    system._progress_callback("Test message", 50, {"test": "metadata"})

    # Verify callback was called with correct parameters
    mock_callback.assert_called_once_with(
        "Test message", 50, {"test": "metadata"}
    )


def test_init_source_based_strategy(monkeypatch):
    """Test initialization with source-based strategy (the current default)."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create settings snapshot for the test
    settings_snapshot = {
        "search.cross_engine_max_results": {"value": 100, "type": "int"},
        "search.cross_engine_use_reddit": {"value": False, "type": "bool"},
        "search.cross_engine_min_date": {"value": None, "type": "str"},
        "search.iterations": {"value": 2, "type": "int"},
        "search.questions_per_iteration": {"value": 3, "type": "int"},
    }

    # Create with default strategy (source-based)
    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        settings_snapshot=settings_snapshot,
    )

    # Check if the correct strategy type was created
    assert "SourceBasedSearchStrategy" in system.strategy.__class__.__name__

    # Also test explicit source-based strategy
    system_explicit = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        strategy_name="source-based",
        settings_snapshot=settings_snapshot,
    )
    assert (
        "SourceBasedSearchStrategy"
        in system_explicit.strategy.__class__.__name__
    )


def test_init_iterdrag_strategy(monkeypatch):
    """Test initialization with IterDRAG strategy."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create with IterDRAG strategy
    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        strategy_name="iterdrag",
    )

    # Check if the correct strategy type was created
    assert "IterDRAGStrategy" in system.strategy.__class__.__name__


def test_init_parallel_strategy(monkeypatch):
    """Test initialization with parallel strategy."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create settings snapshot for the test
    settings_snapshot = {
        "search.cross_engine_max_results": {"value": 100, "type": "int"},
        "search.cross_engine_use_reddit": {"value": False, "type": "bool"},
        "search.cross_engine_min_date": {"value": None, "type": "str"},
        "search.iterations": {"value": 2, "type": "int"},
        "search.questions_per_iteration": {"value": 3, "type": "int"},
    }

    # Create with parallel strategy
    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        strategy_name="parallel",
        settings_snapshot=settings_snapshot,
    )

    # Check if the correct strategy type was created
    assert "ParallelSearchStrategy" in system.strategy.__class__.__name__


def test_init_rapid_strategy(monkeypatch):
    """Test initialization with rapid strategy."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create with rapid strategy
    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        strategy_name="rapid",
    )

    # Check if the correct strategy type was created
    assert "RapidSearchStrategy" in system.strategy.__class__.__name__


def test_init_invalid_strategy(monkeypatch):
    """Test initialization with invalid strategy (should default to source-based)."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking

    # Create with invalid strategy name (will fall through to default)
    # Since StandardStrategy is imported lazily, it won't be available and
    # the system will use whatever is the actual default implementation

    # Skip this test for now as it requires complex mocking of the lazy import
    pytest.skip(
        "Skipping test for invalid strategy as StandardStrategy is deprecated"
    )


def test_set_progress_callback(monkeypatch):
    """Test setting progress callback."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )

    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create settings snapshot for the test
    settings_snapshot = {
        "search.cross_engine_max_results": {"value": 100, "type": "int"},
        "search.cross_engine_use_reddit": {"value": False, "type": "bool"},
        "search.cross_engine_min_date": {"value": None, "type": "str"},
        "search.iterations": {"value": 2, "type": "int"},
        "search.questions_per_iteration": {"value": 3, "type": "int"},
    }

    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        settings_snapshot=settings_snapshot,
    )

    # Create a mock callback
    mock_callback = Mock()

    # Set the callback
    system.set_progress_callback(mock_callback)

    # Verify callback was set on the search system
    assert system.progress_callback == mock_callback

    # Verify callback was passed to the strategy
    assert system.strategy.progress_callback == mock_callback


def test_analyze_topic(monkeypatch):
    """Test analyzing a topic."""
    # Set up mock LLM and search
    mock_llm_instance = Mock()
    mock_search_instance = Mock()

    # Create a mock strategy class and instance
    mock_strategy_class = Mock()
    mock_strategy_instance = Mock()
    mock_strategy_instance.analyze_topic.return_value = {
        "findings": [{"content": "Test finding"}],
        "current_knowledge": "Test knowledge",
        "iterations": 2,
        "questions_by_iteration": {
            1: ["Question 1?", "Question 2?"],
            2: ["Follow-up 1?", "Follow-up 2?"],
        },
        "all_links_of_system": [
            "https://example.com/1",
            "https://example.com/2",
        ],
    }
    # Set the questions_by_iteration attribute on the mock strategy
    mock_strategy_instance.questions_by_iteration = {
        1: ["Question 1?", "Question 2?"],
        2: ["Follow-up 1?", "Follow-up 2?"],
    }
    # Set all_links_of_system attribute on the mock strategy
    mock_strategy_instance.all_links_of_system = [
        "https://example.com/1",
        "https://example.com/2",
    ]
    mock_strategy_class.return_value = mock_strategy_instance

    # Mock get_llm and get_search from their actual modules
    monkeypatch.setattr(
        "local_deep_research.config.llm_config.get_llm",
        lambda **kwargs: mock_llm_instance,
    )
    monkeypatch.setattr(
        "local_deep_research.config.search_config.get_search",
        lambda llm_instance=None, **kwargs: mock_search_instance,
    )
    # Mock SourceBasedSearchStrategy which is now the default
    monkeypatch.setattr(
        "local_deep_research.search_system.SourceBasedSearchStrategy",
        mock_strategy_class,
    )
    # Import after mocking
    from local_deep_research.search_system import AdvancedSearchSystem

    # Create settings snapshot for the test
    settings_snapshot = {
        "search.cross_engine_max_results": {"value": 100, "type": "int"},
        "search.cross_engine_use_reddit": {"value": False, "type": "bool"},
        "search.cross_engine_min_date": {"value": None, "type": "str"},
        "llm.provider": {"value": "test_provider", "type": "str"},
        "llm.model": {"value": "test_model", "type": "str"},
        "search.tool": {"value": "test_search", "type": "str"},
    }

    # Create the search system (uses source-based strategy by default)
    system = AdvancedSearchSystem(
        llm=mock_llm_instance,
        search=mock_search_instance,
        settings_snapshot=settings_snapshot,
    )

    # Set mock all_links_of_system attribute on the strategy
    system.strategy.all_links_of_system = [
        "https://example.com/1",
        "https://example.com/2",
    ]

    # Analyze a topic
    result = system.analyze_topic("test query")

    # Verify strategy's analyze_topic was called
    mock_strategy_instance.analyze_topic.assert_called_once_with("test query")

    # Verify result contents
    assert "findings" in result
    assert "current_knowledge" in result
    assert "iterations" in result
    assert "questions_by_iteration" in result
    assert "search_system" in result

    # Verify search_system reference is correct
    assert result["search_system"] == system

    # Verify questions and links were stored on the system
    assert system.questions_by_iteration == {
        1: ["Question 1?", "Question 2?"],
        2: ["Follow-up 1?", "Follow-up 2?"],
    }
    assert system.all_links_of_system == [
        "https://example.com/1",
        "https://example.com/2",
    ]
