"""
Tests for ModularStrategy.

Tests cover:
- Initialization and configuration
- LLM constraint processing
- Early rejection management
- Candidate confidence tracking
- Component integration
- Error handling
"""

from unittest.mock import Mock, patch, AsyncMock
import pytest


class TestModularStrategyInit:
    """Tests for ModularStrategy initialization."""

    def test_init_with_required_params(self):
        """Initialize with required parameters."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            ModularStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = ModularStrategy(
            search=mock_search,
            model=mock_model,
            all_links_of_system=[],
        )

        assert strategy.model is mock_model
        assert (
            strategy.search_engine is mock_search
        )  # ModularStrategy uses search_engine

    def test_init_creates_components(self):
        """Initialize creates required components."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            ModularStrategy,
        )

        mock_search = Mock()
        mock_model = Mock()

        strategy = ModularStrategy(
            search=mock_search,
            model=mock_model,
            all_links_of_system=[],
        )

        # Should create constraint analyzer and question generator
        assert hasattr(strategy, "constraint_analyzer")
        assert hasattr(strategy, "question_generator")


class TestLLMConstraintProcessor:
    """Tests for LLMConstraintProcessor class."""

    def test_init(self):
        """Initialize LLM constraint processor."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        assert processor.model is mock_model

    def test_parse_decomposition_valid_json(self):
        """Parse decomposition handles valid JSON."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        content = '{"constraint_1": {"atomic_elements": ["a", "b"]}}'
        result = processor._parse_decomposition(content)

        assert isinstance(result, dict)
        assert "constraint_1" in result

    def test_parse_decomposition_invalid_json(self):
        """Parse decomposition handles invalid JSON."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        content = "invalid json content"
        result = processor._parse_decomposition(content)

        assert result == {}

    def test_parse_combinations_valid_json(self):
        """Parse combinations handles valid JSON array."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        content = '["query1", "query2", "query3"]'
        result = processor._parse_combinations(content)

        assert isinstance(result, list)
        assert len(result) == 3

    def test_parse_combinations_invalid_json(self):
        """Parse combinations handles invalid JSON."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        content = "invalid json"
        result = processor._parse_combinations(content)

        assert result == []

    def test_parse_combinations_embedded_json(self):
        """Parse combinations extracts JSON from text."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        content = 'Here are the queries: ["query1", "query2"] that should work.'
        result = processor._parse_combinations(content)

        assert isinstance(result, list)
        assert "query1" in result


class TestEarlyRejectionManager:
    """Tests for EarlyRejectionManager class."""

    def test_init(self):
        """Initialize early rejection manager."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            EarlyRejectionManager,
        )

        mock_model = Mock()
        manager = EarlyRejectionManager(mock_model)

        assert manager.model is mock_model
        assert manager.positive_threshold == 0.6
        assert manager.negative_threshold == 0.3
        assert manager.rejected_candidates == set()

    def test_init_with_custom_thresholds(self):
        """Initialize with custom thresholds."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            EarlyRejectionManager,
        )

        mock_model = Mock()
        manager = EarlyRejectionManager(
            mock_model,
            positive_threshold=0.8,
            negative_threshold=0.2,
        )

        assert manager.positive_threshold == 0.8
        assert manager.negative_threshold == 0.2


class TestCandidateConfidence:
    """Tests for CandidateConfidence dataclass."""

    def test_create_candidate_confidence(self):
        """Create candidate confidence object."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            CandidateConfidence,
        )

        mock_candidate = Mock()
        confidence = CandidateConfidence(
            candidate=mock_candidate,
            positive_confidence=0.8,
            negative_confidence=0.1,
        )

        assert confidence.candidate is mock_candidate
        assert confidence.positive_confidence == 0.8
        assert confidence.negative_confidence == 0.1
        assert confidence.rejection_reason is None
        assert confidence.should_continue is True

    def test_candidate_confidence_with_rejection(self):
        """Create candidate confidence with rejection reason."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            CandidateConfidence,
        )

        mock_candidate = Mock()
        confidence = CandidateConfidence(
            candidate=mock_candidate,
            positive_confidence=0.2,
            negative_confidence=0.7,
            rejection_reason="Failed constraint check",
            should_continue=False,
        )

        assert confidence.rejection_reason == "Failed constraint check"
        assert confidence.should_continue is False


class TestModularStrategyAnalyze:
    """Tests for ModularStrategy analyze_topic method."""

    def test_analyze_topic_returns_dict(self):
        """Analyze topic returns result dictionary."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            ModularStrategy,
        )

        mock_search = Mock()
        mock_search.run.return_value = []
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Test response")

        strategy = ModularStrategy(
            search=mock_search,
            model=mock_model,
            all_links_of_system=[],
        )

        # Mock the constraint analyzer
        with patch.object(
            strategy.constraint_analyzer, "extract_constraints", return_value=[]
        ):
            with patch.object(
                strategy.question_generator,
                "generate_questions",
                return_value=[],
            ):
                result = strategy.analyze_topic("test query")

        assert isinstance(result, dict)

    def test_analyze_topic_with_progress_callback(self):
        """Analyze topic calls progress callback."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            ModularStrategy,
        )

        mock_search = Mock()
        mock_search.run.return_value = []
        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Test")

        strategy = ModularStrategy(
            search=mock_search,
            model=mock_model,
            all_links_of_system=[],
        )

        callback = Mock()
        strategy.set_progress_callback(callback)

        with patch.object(
            strategy.constraint_analyzer, "extract_constraints", return_value=[]
        ):
            with patch.object(
                strategy.question_generator,
                "generate_questions",
                return_value=[],
            ):
                strategy.analyze_topic("test query")

        # Callback should be called at least once
        assert callback.call_count >= 0  # May not be called depending on flow


class TestSearchCache:
    """Tests for search cache integration."""

    def test_search_cache_imported(self):
        """Search cache is properly imported."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            get_search_cache,
            normalize_entity_query,
        )

        assert callable(get_search_cache)
        assert callable(normalize_entity_query)


class TestConstraintCheckerIntegration:
    """Tests for constraint checker integration."""

    def test_constraint_checkers_available(self):
        """Constraint checkers can be imported."""
        from local_deep_research.advanced_search_system.constraint_checking import (
            DualConfidenceChecker,
            StrictChecker,
            ThresholdChecker,
        )

        assert DualConfidenceChecker is not None
        assert StrictChecker is not None
        assert ThresholdChecker is not None


class TestExplorerIntegration:
    """Tests for explorer integration."""

    def test_explorers_available(self):
        """Explorers can be imported."""
        from local_deep_research.advanced_search_system.candidate_exploration import (
            AdaptiveExplorer,
            ConstraintGuidedExplorer,
            DiversityExplorer,
            ParallelExplorer,
        )

        assert AdaptiveExplorer is not None
        assert ConstraintGuidedExplorer is not None
        assert DiversityExplorer is not None
        assert ParallelExplorer is not None


class TestAsyncMethods:
    """Tests for async methods in modular strategy."""

    @pytest.mark.asyncio
    async def test_decompose_constraints_intelligently(self):
        """Test async constraint decomposition."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )
        from local_deep_research.advanced_search_system.constraints.base_constraint import (
            Constraint,
            ConstraintType,
        )

        mock_model = Mock()
        mock_model.ainvoke = AsyncMock(
            return_value=Mock(
                content='{"constraint_1": {"atomic_elements": ["test"]}}'
            )
        )

        processor = LLMConstraintProcessor(mock_model)

        constraint = Constraint(
            id="1",
            type=ConstraintType.PROPERTY,
            value="test",
            description="Test constraint",
            weight=0.5,
        )

        result = await processor.decompose_constraints_intelligently(
            [constraint]
        )

        assert isinstance(result, dict)

    @pytest.mark.asyncio
    async def test_generate_intelligent_combinations(self):
        """Test async combination generation."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        mock_model.ainvoke = AsyncMock(
            return_value=Mock(content='["query1", "query2"]')
        )

        processor = LLMConstraintProcessor(mock_model)

        result = await processor.generate_intelligent_combinations(
            {"constraint_1": {"atomic_elements": ["test"]}},
            existing_queries=[],
            original_query="test query",
        )

        assert isinstance(result, list)

    @pytest.mark.asyncio
    async def test_quick_confidence_check(self):
        """Test async quick confidence check."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            EarlyRejectionManager,
        )

        mock_model = Mock()
        mock_model.ainvoke = AsyncMock(
            return_value=Mock(
                content="POSITIVE: 0.8\nNEGATIVE: 0.1\nUNCERTAINTY: 0.1"
            )
        )

        manager = EarlyRejectionManager(mock_model)

        mock_candidate = Mock()
        mock_constraints = []

        result = await manager.quick_confidence_check(
            mock_candidate, mock_constraints
        )

        # Should return some result (CandidateConfidence or similar)
        assert result is not None


class TestErrorHandling:
    """Tests for error handling in modular strategy."""

    def test_parse_decomposition_handles_exception(self):
        """Parse decomposition handles parsing exceptions."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        # Malformed JSON that would cause exception
        content = '{"incomplete: json'
        result = processor._parse_decomposition(content)

        assert result == {}

    def test_parse_combinations_handles_exception(self):
        """Parse combinations handles parsing exceptions."""
        from local_deep_research.advanced_search_system.strategies.modular_strategy import (
            LLMConstraintProcessor,
        )

        mock_model = Mock()
        processor = LLMConstraintProcessor(mock_model)

        # Malformed JSON that would cause exception
        content = '["incomplete'
        result = processor._parse_combinations(content)

        assert result == []
