"""Tests for error_reporter module."""

from unittest.mock import MagicMock, patch


from local_deep_research.error_handling.error_reporter import (
    ErrorCategory,
)


class TestErrorCategory:
    """Tests for ErrorCategory enum."""

    def test_has_all_expected_categories(self):
        """Should have all expected error categories."""
        expected = [
            "CONNECTION_ERROR",
            "MODEL_ERROR",
            "SEARCH_ERROR",
            "SYNTHESIS_ERROR",
            "FILE_ERROR",
            "RATE_LIMIT_ERROR",
            "UNKNOWN_ERROR",
        ]
        for category in expected:
            assert hasattr(ErrorCategory, category)

    def test_values_are_strings(self):
        """Should have string values."""
        for category in ErrorCategory:
            assert isinstance(category.value, str)


class TestErrorReporterInit:
    """Tests for ErrorReporter initialization."""

    def test_initializes_error_patterns(self, error_reporter):
        """Should initialize with error patterns."""
        assert hasattr(error_reporter, "error_patterns")
        assert isinstance(error_reporter.error_patterns, dict)

    def test_has_patterns_for_all_categories(self, error_reporter):
        """Should have patterns for all categories except UNKNOWN."""
        for category in ErrorCategory:
            if category != ErrorCategory.UNKNOWN_ERROR:
                assert category in error_reporter.error_patterns


class TestCategorizeError:
    """Tests for categorize_error method."""

    def test_categorizes_connection_error(self, error_reporter):
        """Should categorize connection errors."""
        error_messages = [
            "POST predict EOF",
            "Connection refused",
            "timeout waiting for response",
            "[Errno 111] Connection refused",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.CONNECTION_ERROR

    def test_categorizes_model_error(self, error_reporter):
        """Should categorize model errors."""
        error_messages = [
            "Model not found in Ollama",
            "Invalid model specified",
            "API key invalid",
            "401 API key error",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.MODEL_ERROR

    def test_categorizes_rate_limit_error(self, error_reporter):
        """Should categorize rate limit errors."""
        error_messages = [
            "429 resource exhausted",
            "rate limit exceeded",
            "quota exceeded",
            "LLM rate limit reached",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.RATE_LIMIT_ERROR

    def test_categorizes_search_error(self, error_reporter):
        """Should categorize search errors."""
        error_messages = [
            "Search failed",
            "No search results found",
            "The search is longer than 256 characters",
            "GitHub API error occurred",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.SEARCH_ERROR

    def test_categorizes_synthesis_error(self, error_reporter):
        """Should categorize synthesis errors."""
        error_messages = [
            "Error during synthesis process",
            "Failed to generate the report",
            "detailed report stuck at 99%",
            "report taking too long to complete",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.SYNTHESIS_ERROR, (
                f"'{msg}' should be SYNTHESIS_ERROR"
            )

    def test_categorizes_file_error(self, error_reporter):
        """Should categorize file errors."""
        error_messages = [
            "Permission denied",
            "Cannot write to file",
            "Disk is full",
            "No module named local_deep_research",
        ]
        for msg in error_messages:
            result = error_reporter.categorize_error(msg)
            assert result == ErrorCategory.FILE_ERROR, (
                f"'{msg}' should be FILE_ERROR"
            )

    def test_returns_unknown_for_unmatched(self, error_reporter):
        """Should return UNKNOWN_ERROR for unmatched errors."""
        result = error_reporter.categorize_error("Some random error message")
        assert result == ErrorCategory.UNKNOWN_ERROR

    def test_case_insensitive_matching(self, error_reporter):
        """Should match patterns case-insensitively."""
        result = error_reporter.categorize_error("CONNECTION REFUSED")
        assert result == ErrorCategory.CONNECTION_ERROR


class TestGetUserFriendlyTitle:
    """Tests for get_user_friendly_title method."""

    def test_returns_title_for_each_category(self, error_reporter):
        """Should return a title for each category."""
        for category in ErrorCategory:
            result = error_reporter.get_user_friendly_title(category)
            assert isinstance(result, str)
            assert len(result) > 0

    def test_returns_specific_titles(self, error_reporter):
        """Should return specific expected titles."""
        assert (
            error_reporter.get_user_friendly_title(
                ErrorCategory.CONNECTION_ERROR
            )
            == "Connection Issue"
        )
        assert (
            error_reporter.get_user_friendly_title(ErrorCategory.MODEL_ERROR)
            == "LLM Service Error"
        )
        assert (
            error_reporter.get_user_friendly_title(
                ErrorCategory.RATE_LIMIT_ERROR
            )
            == "API Rate Limit Exceeded"
        )


class TestGetSuggestedActions:
    """Tests for get_suggested_actions method."""

    def test_returns_list_for_each_category(self, error_reporter):
        """Should return a list for each category."""
        for category in ErrorCategory:
            result = error_reporter.get_suggested_actions(category)
            assert isinstance(result, list)
            assert len(result) > 0

    def test_suggestions_are_strings(self, error_reporter):
        """Should return list of strings."""
        for category in ErrorCategory:
            suggestions = error_reporter.get_suggested_actions(category)
            for suggestion in suggestions:
                assert isinstance(suggestion, str)


class TestAnalyzeError:
    """Tests for analyze_error method."""

    def test_returns_dict_with_expected_keys(self, error_reporter):
        """Should return dict with expected keys."""
        result = error_reporter.analyze_error("Connection refused")
        expected_keys = [
            "category",
            "title",
            "original_error",
            "suggestions",
            "severity",
            "recoverable",
        ]
        for key in expected_keys:
            assert key in result

    def test_includes_context_when_provided(self, error_reporter):
        """Should include context when provided."""
        context = {"query": "test query", "findings": ["finding1"]}
        result = error_reporter.analyze_error("Connection refused", context)
        assert "context" in result
        assert result["context"] == context

    def test_detects_partial_results(
        self, error_reporter, sample_partial_results
    ):
        """Should detect partial results in context."""
        result = error_reporter.analyze_error(
            "Error occurred", sample_partial_results
        )
        assert result["has_partial_results"] is True

    def test_no_partial_results_when_empty(self, error_reporter):
        """Should not have partial results when context is empty."""
        result = error_reporter.analyze_error("Error occurred", {})
        assert result.get("has_partial_results", False) is False


class TestExtractServiceName:
    """Tests for _extract_service_name method."""

    def test_extracts_openai(self, error_reporter):
        """Should extract OpenAI service name."""
        result = error_reporter._extract_service_name(
            "OpenAI API returned error"
        )
        assert result == "Openai"

    def test_extracts_anthropic(self, error_reporter):
        """Should extract Anthropic service name."""
        result = error_reporter._extract_service_name("Anthropic rate limit")
        assert result == "Anthropic"

    def test_extracts_ollama(self, error_reporter):
        """Should extract Ollama service name."""
        result = error_reporter._extract_service_name(
            "Ollama connection failed"
        )
        assert result == "Ollama"

    def test_returns_default_for_unknown(self, error_reporter):
        """Should return default for unknown service."""
        result = error_reporter._extract_service_name("Unknown error")
        assert result == "API Service"


class TestDetermineSeverity:
    """Tests for _determine_severity method."""

    def test_connection_error_is_high(self, error_reporter):
        """Should return high for connection errors."""
        result = error_reporter._determine_severity(
            ErrorCategory.CONNECTION_ERROR
        )
        assert result == "high"

    def test_model_error_is_high(self, error_reporter):
        """Should return high for model errors."""
        result = error_reporter._determine_severity(ErrorCategory.MODEL_ERROR)
        assert result == "high"

    def test_synthesis_error_is_low(self, error_reporter):
        """Should return low for synthesis errors."""
        result = error_reporter._determine_severity(
            ErrorCategory.SYNTHESIS_ERROR
        )
        assert result == "low"

    def test_rate_limit_is_medium(self, error_reporter):
        """Should return medium for rate limit errors."""
        result = error_reporter._determine_severity(
            ErrorCategory.RATE_LIMIT_ERROR
        )
        assert result == "medium"


class TestIsRecoverable:
    """Tests for _is_recoverable method."""

    def test_unknown_error_not_recoverable(self, error_reporter):
        """Should return False for unknown errors."""
        result = error_reporter._is_recoverable(ErrorCategory.UNKNOWN_ERROR)
        assert result is False

    def test_connection_error_recoverable(self, error_reporter):
        """Should return True for connection errors."""
        result = error_reporter._is_recoverable(ErrorCategory.CONNECTION_ERROR)
        assert result is True

    def test_rate_limit_recoverable(self, error_reporter):
        """Should return True for rate limit errors."""
        result = error_reporter._is_recoverable(ErrorCategory.RATE_LIMIT_ERROR)
        assert result is True


class TestSendErrorNotifications:
    """Tests for _send_error_notifications method."""

    def test_skips_when_no_username(self, error_reporter):
        """Should skip notification when no username in context."""
        # Should not raise, just skip
        error_reporter._send_error_notifications(
            ErrorCategory.MODEL_ERROR, "API key invalid", {}
        )

    def test_skips_non_notifiable_categories(self, error_reporter):
        """Should skip notification for non-notifiable categories."""
        context = {"username": "testuser"}
        # Should not raise, just skip
        error_reporter._send_error_notifications(
            ErrorCategory.SEARCH_ERROR, "Search failed", context
        )

    def test_sends_auth_notification_for_auth_errors(
        self, error_reporter, mock_context_with_username
    ):
        """Should send auth notification for auth errors."""
        # Patch at source locations since these are imported inside the method
        with patch(
            "local_deep_research.database.session_context.get_user_db_session"
        ) as mock_session:
            mock_cm = MagicMock()
            mock_cm.__enter__ = MagicMock(return_value=MagicMock())
            mock_cm.__exit__ = MagicMock(return_value=None)
            mock_session.return_value = mock_cm

            with patch(
                "local_deep_research.settings.SettingsManager"
            ) as mock_settings:
                mock_settings.return_value.get_settings_snapshot.return_value = {}

                with patch(
                    "local_deep_research.notifications.manager.NotificationManager"
                ) as mock_nm:
                    mock_nm_instance = MagicMock()
                    mock_nm.return_value = mock_nm_instance

                    error_reporter._send_error_notifications(
                        ErrorCategory.MODEL_ERROR,
                        "401 API key error",
                        mock_context_with_username,
                    )

                    mock_nm_instance.send_notification.assert_called_once()
