"""
Tests for news/utils/topic_generator.py

Tests cover:
- Topic generation with mocked LLM
- Topic validation and cleaning
- Edge cases: empty input, duplicates, invalid topics
"""

from unittest.mock import patch, MagicMock


class TestValidateTopics:
    """Tests for the _validate_topics function."""

    def test_removes_empty_topics(self):
        """Test that empty topics are removed."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["valid", "", "  ", "another", None]
        # Filter out None before passing
        topics = [t for t in topics if t is not None]
        result = _validate_topics(topics, 5)

        assert "valid" in result
        assert "another" in result
        assert "" not in result

    def test_removes_short_topics(self):
        """Test that topics shorter than 2 chars are removed."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["a", "ab", "abc", "valid topic"]
        result = _validate_topics(topics, 5)

        assert "a" not in result
        assert "ab" in result
        assert "abc" in result
        assert "valid topic" in result

    def test_removes_long_topics(self):
        """Test that topics longer than 30 chars are removed."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        long_topic = "x" * 31
        short_topic = "x" * 30

        topics = [long_topic, short_topic, "normal"]
        result = _validate_topics(topics, 5)

        assert long_topic not in result
        assert "x" * 30 in result
        assert "normal" in result

    def test_removes_case_insensitive_duplicates(self):
        """Test that duplicates are removed case-insensitively."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["Technology", "technology", "TECHNOLOGY", "Other"]
        result = _validate_topics(topics, 5)

        # Should only have one version of technology
        tech_count = sum(1 for t in result if "technolog" in t.lower())
        assert tech_count == 1
        assert "other" in result

    def test_respects_max_topics_limit(self):
        """Test that max_topics limit is enforced."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["one", "two", "three", "four", "five", "six"]
        result = _validate_topics(topics, 3)

        assert len(result) <= 3

    def test_converts_to_lowercase(self):
        """Test that topics are converted to lowercase."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["UPPERCASE", "MixedCase", "lowercase"]
        result = _validate_topics(topics, 5)

        for topic in result:
            assert topic == topic.lower()

    def test_strips_whitespace(self):
        """Test that whitespace is stripped from topics."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        topics = ["  spaced  ", "\ttabbed\t", "normal"]
        result = _validate_topics(topics, 5)

        for topic in result:
            assert topic == topic.strip()

    def test_empty_input_returns_no_valid_topics_message(self):
        """Test that empty input returns failure indicator."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        result = _validate_topics([], 5)

        assert result == ["[No valid topics]"]

    def test_all_invalid_returns_failure_message(self):
        """Test that all invalid topics returns failure indicator."""
        from local_deep_research.news.utils.topic_generator import (
            _validate_topics,
        )

        # All topics are too short
        topics = ["a", "b", "c"]
        result = _validate_topics(topics, 5)

        assert result == ["[No valid topics]"]


class TestGenerateTopics:
    """Tests for the generate_topics function."""

    @patch("local_deep_research.news.utils.topic_generator._generate_with_llm")
    def test_returns_llm_topics_when_successful(self, mock_llm_gen):
        """Test that LLM topics are returned when generation succeeds."""
        from local_deep_research.news.utils.topic_generator import (
            generate_topics,
        )

        mock_llm_gen.return_value = ["tech", "innovation", "startup"]

        result = generate_topics(
            query="tech news", findings="A startup launched a new product"
        )

        assert "tech" in result
        assert "innovation" in result
        assert "startup" in result

    @patch("local_deep_research.news.utils.topic_generator._generate_with_llm")
    def test_returns_failure_message_when_llm_fails(self, mock_llm_gen):
        """Test that failure message is returned when LLM fails."""
        from local_deep_research.news.utils.topic_generator import (
            generate_topics,
        )

        mock_llm_gen.return_value = []

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

        assert "[topic generation failed]" in result


class TestGenerateWithLLM:
    """Tests for the _generate_with_llm function."""

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_parses_json_array_response(self, mock_get_llm):
        """Test parsing of JSON array from LLM."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = '["topic1", "topic2", "topic3"]'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

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

        assert "topic1" in result
        assert "topic2" in result
        assert "topic3" in result

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_handles_json_with_markdown_code_block(self, mock_get_llm):
        """Test handling of JSON wrapped in markdown code block."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = '```json\n["topic1", "topic2"]\n```'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

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

        assert "topic1" in result
        assert "topic2" in result

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_handles_comma_separated_fallback(self, mock_get_llm):
        """Test fallback parsing of comma-separated topics."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = "topic one, topic two, topic three"
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

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

        assert "topic one" in result
        assert "topic two" in result

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_respects_max_topics(self, mock_get_llm):
        """Test that max_topics limit is respected."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = '["t1", "t2", "t3", "t4", "t5", "t6", "t7"]'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

        result = _generate_with_llm("query", "findings", "category", 3)

        assert len(result) <= 3

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_filters_long_topics(self, mock_get_llm):
        """Test that topics longer than 30 chars are filtered."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        long_topic = "x" * 35
        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = f'["short", "{long_topic}"]'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

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

        assert "short" in result
        assert long_topic not in result

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_handles_llm_exception(self, mock_get_llm):
        """Test graceful handling of LLM exceptions."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_get_llm.side_effect = Exception("LLM error")

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

        assert result == []

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_uses_medium_temperature(self, mock_get_llm):
        """Test that medium temperature is used for topic diversity."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = '["topic"]'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

        _generate_with_llm("query", "findings", "category", 5)

        mock_get_llm.assert_called_once_with(
            temperature=0.5, settings_snapshot=None
        )

    @patch("local_deep_research.config.llm_config.get_llm")
    def test_truncates_long_query_in_prompt(self, mock_get_llm):
        """Test that long queries are truncated in prompt."""
        from local_deep_research.news.utils.topic_generator import (
            _generate_with_llm,
        )

        mock_llm = MagicMock()
        mock_response = MagicMock()
        mock_response.content = '["topic"]'
        mock_llm.invoke.return_value = mock_response
        mock_get_llm.return_value = mock_llm

        long_query = "x" * 1000
        _generate_with_llm(long_query, "findings", "category", 5)

        # The prompt should be invoked with truncated query
        call_args = mock_llm.invoke.call_args
        prompt = call_args[0][0]
        # Query should be truncated to 500 chars
        assert "x" * 501 not in prompt
