"""
Tests for atomic_fact_question.py

Tests cover:
- AtomicFactQuestionGenerator initialization
- generate_questions method
- _decompose_to_atomic_facts method
- _generate_gap_filling_questions method
- Question parsing and formatting
"""

from unittest.mock import Mock


class TestAtomicFactQuestionGeneratorInit:
    """Tests for AtomicFactQuestionGenerator initialization."""

    def test_inherits_from_base(self):
        """Test that AtomicFactQuestionGenerator inherits from BaseQuestionGenerator."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )
        from local_deep_research.advanced_search_system.questions.base_question import (
            BaseQuestionGenerator,
        )

        assert issubclass(AtomicFactQuestionGenerator, BaseQuestionGenerator)

    def test_init_with_model(self):
        """Test initialization with model."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        generator = AtomicFactQuestionGenerator(mock_model)

        assert generator.model is mock_model


class TestGenerateQuestions:
    """Tests for generate_questions method."""

    def test_first_iteration_calls_decompose(self):
        """Test that first iteration decomposes query into atomic facts."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Question about geographic features?\nQuestion about naming conventions?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator.generate_questions(
            current_knowledge="",
            query="complex query",
            questions_per_iteration=5,
            questions_by_iteration=None,
        )

        assert len(questions) > 0
        mock_model.invoke.assert_called_once()

    def test_empty_questions_by_iteration_triggers_decompose(self):
        """Test that empty questions_by_iteration triggers decomposition."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Question one?\nQuestion two?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator.generate_questions(
            current_knowledge="some knowledge",
            query="query",
            questions_per_iteration=5,
            questions_by_iteration={},
        )

        assert len(questions) > 0

    def test_subsequent_iteration_calls_gap_filling(self):
        """Test that subsequent iterations generate gap-filling questions."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Gap filling question?\nAnother question?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator.generate_questions(
            current_knowledge="accumulated knowledge",
            query="query",
            questions_per_iteration=3,
            questions_by_iteration={1: ["Previous question?"]},
        )

        assert len(questions) > 0

    def test_respects_questions_per_iteration(self):
        """Test that questions are limited to questions_per_iteration."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Q1?\nQ2?\nQ3?\nQ4?\nQ5?\nQ6?\nQ7?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator.generate_questions(
            current_knowledge="",
            query="query",
            questions_per_iteration=3,
            questions_by_iteration={1: ["prev"]},
        )

        assert len(questions) <= 3

    def test_handles_none_questions_by_iteration(self):
        """Test handling None questions_by_iteration."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question about facts?")

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator.generate_questions(
            current_knowledge="",
            query="query",
            questions_per_iteration=5,
            questions_by_iteration=None,
        )

        # Should default to empty dict and trigger decomposition
        assert isinstance(questions, list)


class TestDecomposeToAtomicFacts:
    """Tests for _decompose_to_atomic_facts method."""

    def test_returns_list_of_questions(self):
        """Test that decomposition returns a list of questions."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="What is the geographic feature?\nWhat is the naming convention?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("complex query")

        assert isinstance(questions, list)
        assert len(questions) > 0

    def test_limits_to_five_facts(self):
        """Test that decomposition is limited to 5 atomic facts."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Q1?\nQ2?\nQ3?\nQ4?\nQ5?\nQ6?\nQ7?\nQ8?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert len(questions) <= 5

    def test_strips_numbering_prefixes(self):
        """Test that numbering prefixes are stripped."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="1. First atomic question\n2. Second atomic question"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        for q in questions:
            assert not q.startswith("1.")
            assert not q.startswith("2.")

    def test_strips_bullet_prefixes(self):
        """Test that bullet prefixes are stripped."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="- Bullet question one\n* Star question two\n• Bullet question three"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        for q in questions:
            assert not q.startswith("-")
            assert not q.startswith("*")
            assert not q.startswith("•")

    def test_filters_short_lines(self):
        """Test that short lines (<=10 chars) are filtered."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="short\nThis is a valid question about atomic facts"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert "short" not in questions
        assert len(questions) == 1

    def test_filters_comments(self):
        """Test that lines starting with # are filtered."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="# This is a comment\nThis is a valid question about facts"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert not any(q.startswith("#") for q in questions)

    def test_handles_response_without_content_attr(self):
        """Test handling response without content attribute."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        # Return a string instead of object with content
        mock_model.invoke.return_value = "Question about geographic facts"

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert isinstance(questions, list)

    def test_handles_empty_response(self):
        """Test handling empty response."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="")

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert questions == []

    def test_includes_query_in_prompt(self):
        """Test that query is included in the prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question result")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._decompose_to_atomic_facts("my specific query")

        call_args = mock_model.invoke.call_args[0][0]
        assert "my specific query" in call_args


class TestGenerateGapFillingQuestions:
    """Tests for _generate_gap_filling_questions method."""

    def test_returns_list_of_questions(self):
        """Test that gap-filling returns a list of questions."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Gap filling question one\nGap filling question two"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=3,
        )

        assert isinstance(questions, list)

    def test_uses_connection_prompt_after_three_iterations(self):
        """Test that connection prompt is used after 3 iterations."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Connecting question?")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"], 2: ["Q2"], 3: ["Q3"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "connect" in call_args.lower() or "fill" in call_args.lower()

    def test_uses_gather_prompt_before_three_iterations(self):
        """Test that gather prompt is used before 3 iterations."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Gathering question?")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "atomic" in call_args.lower() or "gather" in call_args.lower()

    def test_respects_questions_per_iteration_limit(self):
        """Test that questions are limited to questions_per_iteration."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Q1 is long enough\nQ2 is long enough\nQ3 is long enough\nQ4 is long enough\nQ5 is long enough"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=2,
        )

        assert len(questions) <= 2

    def test_strips_numbering_prefixes(self):
        """Test that numbering prefixes are stripped."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="1. First gap question\n2. Second gap question"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=5,
        )

        for q in questions:
            assert not q.startswith("1.")
            assert not q.startswith("2.")

    def test_includes_original_query_in_prompt(self):
        """Test that original query is included in prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question result")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="my original query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "my original query" in call_args

    def test_includes_current_knowledge_in_prompt(self):
        """Test that current knowledge is included in prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question result")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="specific knowledge content",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "specific knowledge content" in call_args

    def test_handles_response_without_content_attr(self):
        """Test handling response without content attribute."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        # Return a string instead of object with content
        mock_model.invoke.return_value = "Gap filling question text"

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=3,
        )

        assert isinstance(questions, list)


class TestQuestionParsing:
    """Tests for question parsing behavior."""

    def test_trims_whitespace(self):
        """Test that whitespace is trimmed from questions."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="  Question with spaces around it  "
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert len(questions) > 0
        assert not questions[0].startswith(" ")
        assert not questions[0].endswith(" ")

    def test_handles_multiple_bullet_types(self):
        """Test handling different bullet types."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="1. Numbered question one\n- Dash bullet question\n* Star bullet question\n• Unicode bullet question"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        for q in questions:
            assert not any(q.startswith(p) for p in ["1.", "-", "*", "•"])

    def test_handles_mixed_content(self):
        """Test handling mixed content with valid and invalid lines."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="# Header comment\nshort\n\nThis is a valid atomic question about geographic features?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert len(questions) == 1
        assert "valid atomic question" in questions[0]


class TestEdgeCases:
    """Tests for edge cases."""

    def test_handles_model_exception(self):
        """Test handling model invocation exception."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.side_effect = Exception("Model error")

        generator = AtomicFactQuestionGenerator(mock_model)

        try:
            generator._decompose_to_atomic_facts("query")
            assert False, "Should raise exception"
        except Exception as e:
            assert "Model error" in str(e)

    def test_handles_whitespace_only_response(self):
        """Test handling whitespace-only response."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="   \n   \n   ")

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert questions == []

    def test_handles_only_short_lines(self):
        """Test handling response with only short lines."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="short1\nshort2\nshort3")

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        # All lines are <= 10 chars, so should be filtered
        assert questions == []

    def test_handles_exactly_five_questions(self):
        """Test handling exactly 5 questions."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(
            content="Question one about facts?\nQuestion two about facts?\nQuestion three about facts?\nQuestion four about facts?\nQuestion five about facts?"
        )

        generator = AtomicFactQuestionGenerator(mock_model)

        questions = generator._decompose_to_atomic_facts("query")

        assert len(questions) == 5

    def test_two_iterations_uses_gather_prompt(self):
        """Test that exactly 2 iterations uses gather prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Gathering question?")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"], 2: ["Q2"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        # 2 iterations is < 3, so should use gather prompt
        assert "atomic" in call_args.lower() or "gather" in call_args.lower()

    def test_three_iterations_uses_connection_prompt(self):
        """Test that exactly 3 iterations uses connection prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Connection question?")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"], 2: ["Q2"], 3: ["Q3"]},
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        # 3 iterations is >= 3, so should use connection prompt
        assert "connect" in call_args.lower() or "fill" in call_args.lower()

    def test_includes_questions_per_iteration_in_prompt(self):
        """Test that questions_per_iteration is included in prompt."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question result")

        generator = AtomicFactQuestionGenerator(mock_model)

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration={1: ["Q1"]},
            questions_per_iteration=7,
        )

        call_args = mock_model.invoke.call_args[0][0]
        assert "7" in call_args


class TestFormatPreviousQuestions:
    """Tests for _format_previous_questions helper (inherited from base)."""

    def test_formats_questions_by_iteration(self):
        """Test that previous questions are formatted."""
        from local_deep_research.advanced_search_system.questions.atomic_fact_question import (
            AtomicFactQuestionGenerator,
        )

        mock_model = Mock()
        mock_model.invoke.return_value = Mock(content="Question result")

        generator = AtomicFactQuestionGenerator(mock_model)

        questions_by_iteration = {1: ["Q1", "Q2"], 2: ["Q3"]}

        generator._generate_gap_filling_questions(
            original_query="query",
            current_knowledge="knowledge",
            questions_by_iteration=questions_by_iteration,
            questions_per_iteration=3,
        )

        call_args = mock_model.invoke.call_args[0][0]
        # Previous questions should be formatted and included
        assert "Q1" in call_args or "Previous" in call_args
