"""
Extended tests for news/utils/topic_generator.py

Tests cover:
- generate_topics() function
- _generate_with_llm() helper
- _validate_topics() helper
- Topic cleaning and validation
- Edge cases and error handling
"""

from unittest.mock import Mock, patch


class TestGenerateTopicsBasic:
    """Basic tests for generate_topics() function."""

    def test_returns_list(self):
        """generate_topics returns a list."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = ["Topic 1", "Topic 2"]

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("test query")

            assert isinstance(result, list)

    def test_uses_llm_generated_topics(self):
        """Uses LLM-generated topics when available."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = ["AI", "Technology", "Research"]

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("query", "findings")

            # Topics get lowercased by _validate_topics
            assert "ai" in result or "AI" in result

    def test_returns_failure_message_when_llm_fails(self):
        """Returns failure message when LLM fails."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = []

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("query", "findings")

            assert any(
                "failed" in t.lower() or "no valid" in t.lower() for t in result
            )

    def test_accepts_category_parameter(self):
        """Accepts category parameter."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = ["Tech News"]

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            generate_topics("query", "findings", category="technology")

            mock_gen.assert_called_once()

    def test_respects_max_topics_parameter(self):
        """Respects max_topics parameter."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = ["AA", "BB", "CC", "DD", "EE", "FF"]

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("query", max_topics=3)

            assert len(result) <= 3


class TestGenerateWithLLMFunction:
    """Tests for _generate_with_llm() helper function."""

    def test_function_exists(self):
        """_generate_with_llm function exists."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        assert callable(_generate_with_llm)

    def test_returns_list(self):
        """_generate_with_llm returns a list."""
        with patch(
            "local_deep_research.config.llm_config.get_llm"
        ) as mock_get_llm:
            mock_llm = Mock()
            mock_llm.invoke.return_value = Mock(
                content='["Topic 1", "Topic 2"]'
            )
            mock_get_llm.return_value = mock_llm

            from local_deep_research.news.utils.topic_generator import (
                _generate_with_llm,
            )

            result = _generate_with_llm("query", "findings", "", 5)

            # May return list or empty list depending on LLM response
            assert isinstance(result, list)

    def test_returns_empty_on_error(self):
        """_generate_with_llm returns empty list on error."""
        with patch(
            "local_deep_research.config.llm_config.get_llm"
        ) as mock_get_llm:
            mock_get_llm.side_effect = Exception("LLM error")

            from local_deep_research.news.utils.topic_generator import (
                _generate_with_llm,
            )

            result = _generate_with_llm("query", "findings", "", 5)

            assert result == []


class TestValidateTopics:
    """Tests for _validate_topics() helper."""

    def test_removes_empty_topics(self):
        """Removes empty string topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["Topic", "", "  ", "Valid"], 5)

        assert "" not in result
        assert "  " not in result

    def test_removes_short_topics(self):
        """Removes topics shorter than 2 characters."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["AI", "A", "AB", "ABC"], 5)

        assert "a" not in result  # Single char removed
        assert "ab" in result  # 2 chars kept
        assert "abc" in result  # 3 chars kept

    def test_removes_long_topics(self):
        """Removes topics longer than 30 characters."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        long_topic = "A" * 35
        result = _validate_topics(["Short", long_topic], 5)

        assert "short" in result
        assert long_topic.lower() not in result

    def test_removes_duplicates_case_insensitive(self):
        """Removes duplicate topics (case-insensitive)."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["AI", "ai", "Ai", "Technology"], 5)

        assert result.count("ai") == 1

    def test_converts_to_lowercase(self):
        """Converts topics to lowercase."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["TECHNOLOGY", "Science"], 5)

        assert "technology" in result
        assert "science" in result
        assert "TECHNOLOGY" not in result

    def test_respects_max_topics(self):
        """Respects max_topics limit."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(
            ["A1", "B2", "C3", "D4", "E5", "F6", "G7"], max_topics=3
        )

        assert len(result) == 3

    def test_returns_no_valid_topics_message(self):
        """Returns failure message when no valid topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["", "  ", "A"], 5)  # All invalid

        assert "[No valid topics]" in result


class TestTopicGeneratorEdgeCases:
    """Edge case tests for topic generation."""

    def test_empty_query(self):
        """Handles empty query."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = []

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("")

            assert isinstance(result, list)

    def test_unicode_topics(self):
        """Handles unicode topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["日本", "économie", "科技"], 5)

        assert "日本" in result
        assert "économie" in result

    def test_topics_with_special_characters(self):
        """Handles topics with special characters."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["AI-ML", "COVID-19", "C++"], 5)

        # Topics are cleaned and lowercased
        assert any("ai" in t or "ml" in t for t in result) or len(result) > 0

    def test_max_topics_zero(self):
        """Handles max_topics of zero."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["Topic 1", "Topic 2"], 0)

        # Should return no valid topics message or empty behavior
        assert len(result) <= 1


class TestTopicGeneratorIntegration:
    """Integration tests for topic generation."""

    def test_full_flow_with_llm_mock(self):
        """Full flow with mocked LLM generation."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = [
                "Artificial Intelligence",
                "Machine Learning",
                "Technology",
            ]

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics(
                "AI research news",
                "Recent developments in AI and machine learning...",
                category="technology",
                max_topics=5,
            )

            assert len(result) > 0
            assert "artificial intelligence" in result

    def test_full_flow_with_llm_failure(self):
        """Full flow when LLM fails."""
        with patch(
            "local_deep_research.news.utils.topic_generator._generate_with_llm"
        ) as mock_gen:
            mock_gen.return_value = []  # LLM failed, returns empty

            from local_deep_research.news.utils.topic_generator import (
                generate_topics,
            )

            result = generate_topics("query", "findings")

            # Should return failure message - either "failed" or "no valid"
            assert isinstance(result, list)
            assert len(result) > 0
            has_failure_indicator = any(
                "failed" in t.lower() or "no valid" in t.lower() or "[" in t
                for t in result
            )
            assert has_failure_indicator


class TestTopicValidationDetails:
    """Detailed tests for topic validation."""

    def test_strips_whitespace(self):
        """Strips whitespace from topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["  Topic  ", "\tAnother\n"], 5)

        assert "topic" in result
        assert "another" in result

    def test_preserves_order(self):
        """Preserves order of topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["First", "Second", "Third"], 5)

        assert result[0] == "first"
        assert result[1] == "second"
        assert result[2] == "third"

    def test_handles_only_invalid_topics(self):
        """Handles list with only invalid topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["", " ", "X"], 5)

        # All invalid - should get failure message
        assert "[No valid topics]" in result

    def test_exact_max_topics_boundary(self):
        """Handles exactly max_topics number of topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics(["AA", "BB", "CC"], 3)

        assert len(result) == 3
