"""Extended tests for domain_classifier/classifier.py - targeting untested paths.

Covers:
- classify_all_domains() - completely untested (lines 276-407)
- classify_domain() with force_update=True
- get_categories_summary() - subcategory aggregation
- _get_domain_samples() edge cases
"""

from unittest.mock import MagicMock, Mock, patch


# ── classify_all_domains ──────────────────────────────────────────


class TestClassifyAllDomains:
    """Tests for classify_all_domains (lines 276-407, entirely untested)."""

    def _make_classifier(self):
        from local_deep_research.domain_classifier.classifier import (
            DomainClassifier,
        )

        return DomainClassifier(username="testuser", settings_snapshot={})

    def test_empty_database_returns_zero_total(self):
        """No resources → total=0, no processing."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = []

        with patch(
            "local_deep_research.domain_classifier.classifier.get_user_db_session"
        ) as mock_gs:
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains()

        assert result["total"] == 0
        assert result["classified"] == 0
        assert result["failed"] == 0
        assert result["skipped"] == 0

    def test_extracts_domains_from_urls(self):
        """Domains are extracted and normalized from resource URLs."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://www.example.com/page1",),
            ("https://www.example.com/page2",),
            ("https://other.org/doc",),
        ]
        # For the domain query in classify_all_domains, we need to handle
        # the subsequent query for DomainClassification filter_by
        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(classifier, "classify_domain", return_value=None),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=True)

        # www.example.com → example.com (deduplicated), other.org → 2 unique domains
        assert result["total"] == 2

    def test_progress_callback_invoked(self):
        """Progress callback is called for each domain with percentage."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://a.com/page",),
            ("https://b.com/page",),
        ]

        callback = Mock()

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(classifier, "classify_domain", return_value=None),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            classifier.classify_all_domains(
                force_update=True, progress_callback=callback
            )

        assert callback.call_count == 2
        first_call = callback.call_args_list[0][0][0]
        assert "current" in first_call
        assert "total" in first_call
        assert "percentage" in first_call

    def test_skips_already_classified_without_force(self):
        """Without force_update, already-classified domains are skipped."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://known.com/page",),
        ]

        existing = Mock()
        existing.category = "Technology"
        existing.subcategory = "Software Development"
        mock_session.query.return_value.filter_by.return_value.first.return_value = existing

        with patch(
            "local_deep_research.domain_classifier.classifier.get_user_db_session"
        ) as mock_gs:
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=False)

        assert result["skipped"] == 1
        assert result["classified"] == 0

    def test_successful_classification_counted(self):
        """Successful classify_domain calls increment 'classified' counter."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://new-site.com/page",),
        ]

        mock_classification = Mock()
        mock_classification.category = "Technology"
        mock_classification.subcategory = "Cloud Service"
        mock_classification.confidence = 0.9

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(
                classifier,
                "classify_domain",
                return_value=mock_classification,
            ),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=True)

        assert result["classified"] == 1
        assert result["domains"][0]["status"] == "classified"
        assert result["domains"][0]["category"] == "Technology"

    def test_failed_classification_counted(self):
        """Failed classify_domain calls increment 'failed' counter."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://bad-site.com/page",),
        ]

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(classifier, "classify_domain", return_value=None),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=True)

        assert result["failed"] == 1
        assert result["domains"][0]["status"] == "failed"

    def test_exception_in_domain_increments_failed(self):
        """Exception during single domain classification is caught and counted."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("https://crash.com/page",),
        ]

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(
                classifier,
                "classify_domain",
                side_effect=RuntimeError("LLM error"),
            ),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=True)

        assert result["failed"] == 1
        assert result["domains"][0]["status"] == "failed"

    def test_invalid_url_skipped_gracefully(self):
        """URLs that fail parsing are skipped without crashing."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.distinct.return_value.all.return_value = [
            ("not a valid url",),
            (None,),
            ("https://valid.com/page",),
        ]

        with (
            patch(
                "local_deep_research.domain_classifier.classifier.get_user_db_session"
            ) as mock_gs,
            patch.object(classifier, "classify_domain", return_value=None),
        ):
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.classify_all_domains(force_update=True)

        # "not a valid url" has empty netloc, None is skipped → only valid.com
        assert result["total"] == 1


# ── get_categories_summary: subcategory aggregation ───────────────


class TestGetCategoriesSummary:
    """Tests for get_categories_summary subcategory aggregation (lines 449-488)."""

    def _make_classifier(self):
        from local_deep_research.domain_classifier.classifier import (
            DomainClassifier,
        )

        return DomainClassifier(username="testuser")

    def test_multiple_domains_same_category(self):
        """Multiple domains in the same category are aggregated."""
        classifier = self._make_classifier()

        c1 = Mock(
            domain="a.com",
            category="Technology",
            subcategory="Cloud",
            confidence=0.9,
        )
        c2 = Mock(
            domain="b.com",
            category="Technology",
            subcategory="SaaS",
            confidence=0.8,
        )

        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = [c1, c2]

        with patch(
            "local_deep_research.domain_classifier.classifier.get_user_db_session"
        ) as mock_gs:
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.get_categories_summary()

        assert result["Technology"]["count"] == 2
        assert result["Technology"]["subcategories"]["Cloud"] == 1
        assert result["Technology"]["subcategories"]["SaaS"] == 1

    def test_none_subcategory_skipped(self):
        """None subcategory does not appear in subcategories dict."""
        classifier = self._make_classifier()

        c1 = Mock(
            domain="x.com", category="Other", subcategory=None, confidence=0.5
        )

        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = [c1]

        with patch(
            "local_deep_research.domain_classifier.classifier.get_user_db_session"
        ) as mock_gs:
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.get_categories_summary()

        assert result["Other"]["count"] == 1
        assert result["Other"]["subcategories"] == {}

    def test_empty_database_returns_empty(self):
        """No classifications → empty summary."""
        classifier = self._make_classifier()

        mock_session = MagicMock()
        mock_session.query.return_value.all.return_value = []

        with patch(
            "local_deep_research.domain_classifier.classifier.get_user_db_session"
        ) as mock_gs:
            mock_gs.return_value.__enter__ = Mock(return_value=mock_session)
            mock_gs.return_value.__exit__ = Mock(return_value=False)

            result = classifier.get_categories_summary()

        assert result == {}
