"""
Tests for Q:-prefix parsing in generate_questions and numbered/dash format
parsing in generate_sub_questions.

Source: src/local_deep_research/advanced_search_system/questions/standard_question.py
"""

from unittest.mock import Mock

import pytest

from local_deep_research.advanced_search_system.questions.standard_question import (
    StandardQuestionGenerator,
)


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def generator():
    """Create StandardQuestionGenerator with a mock LLM."""
    mock_llm = Mock()
    gen = StandardQuestionGenerator.__new__(StandardQuestionGenerator)
    gen.model = mock_llm
    return gen


# ---------------------------------------------------------------------------
# generate_questions — Q: prefix parsing
# ---------------------------------------------------------------------------


class TestGenerateQuestionsQPrefix:
    """Test Q:-prefix parsing edge cases."""

    def test_standard_q_prefix(self, generator):
        """Standard 'Q: question' format parsed correctly."""
        generator.model.invoke.return_value = Mock(
            content="Q: What is AI?\nQ: How does ML work?"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        assert result == ["What is AI?", "How does ML work?"]

    def test_q_prefix_no_space_after_colon(self, generator):
        """'Q:question' with no space after colon still works."""
        generator.model.invoke.return_value = Mock(
            content="Q:What is AI?\nQ:How does ML work?"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        assert result == ["What is AI?", "How does ML work?"]

    def test_leading_whitespace_before_q(self, generator):
        """'  Q: question' with leading whitespace still matches."""
        generator.model.invoke.return_value = Mock(
            content="  Q: What is AI?\n  Q: How does ML work?"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        # strip() on line then startswith("Q:") should work
        assert len(result) == 2
        assert result[0] == "What is AI?"

    def test_q_midline_ignored(self, generator):
        """'The Q: is not a question' — Q: mid-line should not match."""
        generator.model.invoke.return_value = Mock(
            content="This has Q: mid-line\nQ: Real question"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        # "This has Q: mid-line" does NOT start with "Q:" so should be skipped
        assert len(result) == 1
        assert result[0] == "Real question"

    def test_empty_q_line_produces_empty_string(self, generator):
        """'Q: ' (just Q: with space) produces empty string — potential bug."""
        generator.model.invoke.return_value = Mock(
            content="Q: \nQ: Real question"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        assert len(result) == 2
        # First question is empty string after replace("Q:", "").strip()
        assert result[0] == ""
        assert result[1] == "Real question"

    def test_respects_questions_per_iteration_limit(self, generator):
        """Only returns up to questions_per_iteration questions."""
        generator.model.invoke.return_value = Mock(
            content="Q: Q1\nQ: Q2\nQ: Q3\nQ: Q4"
        )
        result = generator.generate_questions("knowledge", "query", 2)
        assert len(result) == 2

    def test_string_response_no_content_attr(self, generator):
        """Response without .content attribute uses str()."""
        generator.model.invoke.return_value = "Q: Plain string question"
        result = generator.generate_questions("knowledge", "query", 1)
        assert len(result) == 1
        assert result[0] == "Plain string question"

    def test_with_questions_by_iteration(self, generator):
        """Passing questions_by_iteration changes prompt but parsing is same."""
        generator.model.invoke.return_value = Mock(
            content="Q: Follow-up question"
        )
        result = generator.generate_questions(
            "knowledge",
            "query",
            1,
            questions_by_iteration={1: ["Previous question"]},
        )
        assert result == ["Follow-up question"]


# ---------------------------------------------------------------------------
# generate_sub_questions — numbered/dash format parsing
# ---------------------------------------------------------------------------


class TestGenerateSubQuestions:
    """Test sub-question parsing from numbered and bulleted formats."""

    def test_standard_numbered_format(self, generator):
        """'1. question' parsed correctly using split('.', 1)."""
        generator.model.invoke.return_value = Mock(
            content="1. What is X?\n2. How does Y work?\n3. Why is Z important?"
        )
        result = generator.generate_sub_questions("complex query")
        assert len(result) == 3
        assert result[0] == "What is X?"
        assert result[1] == "How does Y work?"

    def test_parenthesis_format(self, generator):
        """'1) question' format — digit is detected, split by '.' or ' '."""
        generator.model.invoke.return_value = Mock(
            content="1) What is X?\n2) How does Y work?"
        )
        result = generator.generate_sub_questions("query")
        # '1) What is X?' — line[0].isdigit()=True, no '.' so split(' ', 1)
        # parts = ['1)', 'What is X?'] -> parts[1] = 'What is X?'
        assert len(result) == 2
        assert result[0] == "What is X?"

    def test_numbered_with_no_content(self, generator):
        """'1.' with no text after dot."""
        generator.model.invoke.return_value = Mock(
            content="1.\n2. Real question"
        )
        result = generator.generate_sub_questions("query")
        # '1.' -> split('.', 1) -> ['1', ''] -> parts[1].strip() = ''
        # Empty string is still appended
        assert any(q == "" for q in result) or len(result) == 1

    def test_dash_format(self, generator):
        """'- question' format parsed correctly."""
        generator.model.invoke.return_value = Mock(
            content="- What is X?\n- How does Y work?"
        )
        result = generator.generate_sub_questions("query")
        assert len(result) == 2

    def test_lines_without_digit_or_dash_skipped(self, generator):
        """Lines not starting with digit or dash are skipped."""
        generator.model.invoke.return_value = Mock(
            content="Here are the sub-questions:\n1. What is X?\nNote: these are important\n2. How?"
        )
        result = generator.generate_sub_questions("query")
        # Only lines starting with digit or dash are parsed
        assert len(result) == 2

    def test_max_5_sub_questions(self, generator):
        """At most 5 sub-questions returned."""
        generator.model.invoke.return_value = Mock(
            content="\n".join(f"{i}. Question {i}" for i in range(1, 10))
        )
        result = generator.generate_sub_questions("query")
        assert len(result) <= 5

    def test_exception_returns_empty_list(self, generator):
        """Exception during generation returns empty list."""
        generator.model.invoke.side_effect = RuntimeError("LLM error")
        result = generator.generate_sub_questions("query")
        assert result == []

    def test_string_response_handling(self, generator):
        """Response without .content attribute uses str()."""
        generator.model.invoke.return_value = (
            "1. Sub question one\n2. Sub question two"
        )
        result = generator.generate_sub_questions("query")
        assert len(result) == 2

    def test_with_context(self, generator):
        """Context parameter is passed to prompt."""
        generator.model.invoke.return_value = Mock(
            content="1. Question with context"
        )
        result = generator.generate_sub_questions(
            "query", context="Some context"
        )
        assert len(result) == 1
        # Verify context was included in the prompt
        call_args = generator.model.invoke.call_args[0][0]
        assert "Some context" in call_args
