"""
Comprehensive tests for news module exceptions.
Tests all exception classes, their attributes, methods, and inheritance.
"""

import pytest


class TestNewsAPIExceptionBase:
    """Tests for NewsAPIException base class."""

    def test_creates_with_message_only(self):
        """Test creating exception with just message."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        assert exc.message == "Test error"
        assert str(exc) == "Test error"

    def test_default_status_code_is_500(self):
        """Test default status code is 500."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        assert exc.status_code == 500

    def test_custom_status_code(self):
        """Test setting custom status code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Not found", status_code=404)
        assert exc.status_code == 404

    def test_default_error_code_is_class_name(self):
        """Test default error_code is class name."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        assert exc.error_code == "NewsAPIException"

    def test_custom_error_code(self):
        """Test setting custom error code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error", error_code="CUSTOM_ERROR")
        assert exc.error_code == "CUSTOM_ERROR"

    def test_default_details_is_empty_dict(self):
        """Test default details is empty dict."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        assert exc.details == {}

    def test_custom_details(self):
        """Test setting custom details."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException(
            "Test error", details={"key": "value", "count": 42}
        )
        assert exc.details == {"key": "value", "count": 42}

    def test_is_exception_subclass(self):
        """Test is subclass of Exception."""
        from local_deep_research.news.exceptions import NewsAPIException

        assert issubclass(NewsAPIException, Exception)

    def test_can_be_raised(self):
        """Test exception can be raised and caught."""
        from local_deep_research.news.exceptions import NewsAPIException

        with pytest.raises(NewsAPIException):
            raise NewsAPIException("Test error")

    def test_to_dict_basic(self):
        """Test to_dict returns basic fields."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        result = exc.to_dict()

        assert result["error"] == "Test error"
        assert result["error_code"] == "NewsAPIException"
        assert result["status_code"] == 500

    def test_to_dict_with_details(self):
        """Test to_dict includes details when present."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error", details={"key": "value"})
        result = exc.to_dict()

        assert result["details"] == {"key": "value"}

    def test_to_dict_without_details(self):
        """Test to_dict excludes details key when empty."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Test error")
        result = exc.to_dict()

        assert "details" not in result

    def test_all_parameters(self):
        """Test creating with all parameters."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException(
            message="Full error",
            status_code=422,
            error_code="FULL_ERROR",
            details={"field": "value"},
        )

        assert exc.message == "Full error"
        assert exc.status_code == 422
        assert exc.error_code == "FULL_ERROR"
        assert exc.details == {"field": "value"}


class TestNewsFeatureDisabledException:
    """Tests for NewsFeatureDisabledException."""

    def test_default_message(self):
        """Test default message."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
        )

        exc = NewsFeatureDisabledException()
        assert exc.message == "News system is disabled"

    def test_custom_message(self):
        """Test custom message."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
        )

        exc = NewsFeatureDisabledException("Custom disabled message")
        assert exc.message == "Custom disabled message"

    def test_status_code_is_503(self):
        """Test status code is 503 (Service Unavailable)."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
        )

        exc = NewsFeatureDisabledException()
        assert exc.status_code == 503

    def test_error_code(self):
        """Test error code is NEWS_DISABLED."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
        )

        exc = NewsFeatureDisabledException()
        assert exc.error_code == "NEWS_DISABLED"

    def test_inherits_from_base(self):
        """Test inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
            NewsAPIException,
        )

        assert issubclass(NewsFeatureDisabledException, NewsAPIException)


class TestInvalidLimitException:
    """Tests for InvalidLimitException."""

    def test_message_includes_limit(self):
        """Test message includes the provided limit."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(0)
        assert "0" in exc.message
        assert "Limit must be at least 1" in exc.message

    def test_status_code_is_400(self):
        """Test status code is 400 (Bad Request)."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(-5)
        assert exc.status_code == 400

    def test_error_code(self):
        """Test error code is INVALID_LIMIT."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(0)
        assert exc.error_code == "INVALID_LIMIT"

    def test_details_include_provided_limit(self):
        """Test details include provided_limit."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(-10)
        assert exc.details["provided_limit"] == -10

    def test_details_include_min_limit(self):
        """Test details include min_limit."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(0)
        assert exc.details["min_limit"] == 1

    def test_various_invalid_limits(self):
        """Test with various invalid limit values."""
        from local_deep_research.news.exceptions import InvalidLimitException

        for limit in [0, -1, -100, -999999]:
            exc = InvalidLimitException(limit)
            assert exc.details["provided_limit"] == limit


class TestSubscriptionNotFoundException:
    """Tests for SubscriptionNotFoundException."""

    def test_message_includes_id(self):
        """Test message includes subscription ID."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        exc = SubscriptionNotFoundException("sub-123")
        assert "sub-123" in exc.message

    def test_status_code_is_404(self):
        """Test status code is 404 (Not Found)."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        exc = SubscriptionNotFoundException("sub-123")
        assert exc.status_code == 404

    def test_error_code(self):
        """Test error code is SUBSCRIPTION_NOT_FOUND."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        exc = SubscriptionNotFoundException("sub-123")
        assert exc.error_code == "SUBSCRIPTION_NOT_FOUND"

    def test_details_include_subscription_id(self):
        """Test details include subscription_id."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        exc = SubscriptionNotFoundException("my-sub-id")
        assert exc.details["subscription_id"] == "my-sub-id"


class TestSubscriptionCreationException:
    """Tests for SubscriptionCreationException."""

    def test_message_includes_reason(self):
        """Test message includes the reason."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException("duplicate key")
        assert "duplicate key" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException("error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is SUBSCRIPTION_CREATE_FAILED."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException("error")
        assert exc.error_code == "SUBSCRIPTION_CREATE_FAILED"

    def test_custom_details(self):
        """Test can pass custom details."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException(
            "error", details={"query": "test", "user_id": "user1"}
        )
        assert exc.details["query"] == "test"
        assert exc.details["user_id"] == "user1"


class TestSubscriptionUpdateException:
    """Tests for SubscriptionUpdateException."""

    def test_message_includes_id_and_reason(self):
        """Test message includes subscription ID and reason."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-456", "invalid field")
        assert "sub-456" in exc.message
        assert "invalid field" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-123", "error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is SUBSCRIPTION_UPDATE_FAILED."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-123", "error")
        assert exc.error_code == "SUBSCRIPTION_UPDATE_FAILED"

    def test_details_include_subscription_id(self):
        """Test details include subscription_id."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-789", "error")
        assert exc.details["subscription_id"] == "sub-789"


class TestSubscriptionDeletionException:
    """Tests for SubscriptionDeletionException."""

    def test_message_includes_id_and_reason(self):
        """Test message includes subscription ID and reason."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
        )

        exc = SubscriptionDeletionException("sub-111", "foreign key constraint")
        assert "sub-111" in exc.message
        assert "foreign key constraint" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
        )

        exc = SubscriptionDeletionException("sub-123", "error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is SUBSCRIPTION_DELETE_FAILED."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
        )

        exc = SubscriptionDeletionException("sub-123", "error")
        assert exc.error_code == "SUBSCRIPTION_DELETE_FAILED"


class TestDatabaseAccessException:
    """Tests for DatabaseAccessException."""

    def test_message_includes_operation_and_reason(self):
        """Test message includes operation and reason."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("insert", "connection refused")
        assert "insert" in exc.message
        assert "connection refused" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("query", "error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is DATABASE_ERROR."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("query", "error")
        assert exc.error_code == "DATABASE_ERROR"

    def test_details_include_operation(self):
        """Test details include operation."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("fetch_all", "timeout")
        assert exc.details["operation"] == "fetch_all"


class TestNewsFeedGenerationException:
    """Tests for NewsFeedGenerationException."""

    def test_message_includes_reason(self):
        """Test message includes reason."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("no sources available")
        assert "no sources available" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is FEED_GENERATION_FAILED."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("error")
        assert exc.error_code == "FEED_GENERATION_FAILED"

    def test_details_include_user_id_when_provided(self):
        """Test details include user_id when provided."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("error", user_id="user123")
        assert exc.details["user_id"] == "user123"

    def test_details_empty_when_no_user_id(self):
        """Test details empty when no user_id provided."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("error")
        assert exc.details == {}


class TestResearchProcessingException:
    """Tests for ResearchProcessingException."""

    def test_message_includes_reason(self):
        """Test message includes reason."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
        )

        exc = ResearchProcessingException("parsing failed")
        assert "parsing failed" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
        )

        exc = ResearchProcessingException("error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is RESEARCH_PROCESSING_FAILED."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
        )

        exc = ResearchProcessingException("error")
        assert exc.error_code == "RESEARCH_PROCESSING_FAILED"

    def test_details_include_research_id_when_provided(self):
        """Test details include research_id when provided."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
        )

        exc = ResearchProcessingException("error", research_id="res-456")
        assert exc.details["research_id"] == "res-456"


class TestNotImplementedException:
    """Tests for NotImplementedException."""

    def test_message_includes_feature(self):
        """Test message includes feature name."""
        from local_deep_research.news.exceptions import NotImplementedException

        exc = NotImplementedException("advanced_search")
        assert "advanced_search" in exc.message

    def test_status_code_is_501(self):
        """Test status code is 501 (Not Implemented)."""
        from local_deep_research.news.exceptions import NotImplementedException

        exc = NotImplementedException("feature")
        assert exc.status_code == 501

    def test_error_code(self):
        """Test error code is NOT_IMPLEMENTED."""
        from local_deep_research.news.exceptions import NotImplementedException

        exc = NotImplementedException("feature")
        assert exc.error_code == "NOT_IMPLEMENTED"

    def test_details_include_feature(self):
        """Test details include feature name."""
        from local_deep_research.news.exceptions import NotImplementedException

        exc = NotImplementedException("export_to_pdf")
        assert exc.details["feature"] == "export_to_pdf"


class TestInvalidParameterException:
    """Tests for InvalidParameterException."""

    def test_message_includes_parameter_and_reason(self):
        """Test message includes parameter name and reason."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("limit", -5, "must be positive")
        assert "limit" in exc.message
        assert "must be positive" in exc.message

    def test_status_code_is_400(self):
        """Test status code is 400."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("param", "val", "invalid")
        assert exc.status_code == 400

    def test_error_code(self):
        """Test error code is INVALID_PARAMETER."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("param", "val", "invalid")
        assert exc.error_code == "INVALID_PARAMETER"

    def test_details_include_parameter_and_value(self):
        """Test details include parameter and value."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("offset", -10, "must be non-negative")
        assert exc.details["parameter"] == "offset"
        assert exc.details["value"] == -10


class TestSchedulerNotificationException:
    """Tests for SchedulerNotificationException."""

    def test_message_includes_action_and_reason(self):
        """Test message includes action and reason."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("start_job", "scheduler offline")
        assert "start_job" in exc.message
        assert "scheduler offline" in exc.message

    def test_status_code_is_500(self):
        """Test status code is 500."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("action", "error")
        assert exc.status_code == 500

    def test_error_code(self):
        """Test error code is SCHEDULER_NOTIFICATION_FAILED."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("action", "error")
        assert exc.error_code == "SCHEDULER_NOTIFICATION_FAILED"

    def test_details_include_action(self):
        """Test details include action."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("reschedule", "error")
        assert exc.details["action"] == "reschedule"


class TestExceptionInheritanceChain:
    """Tests for exception inheritance."""

    def test_all_exceptions_inherit_from_base(self):
        """Test all custom exceptions inherit from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            NewsAPIException,
            NewsFeatureDisabledException,
            InvalidLimitException,
            SubscriptionNotFoundException,
            SubscriptionCreationException,
            SubscriptionUpdateException,
            SubscriptionDeletionException,
            DatabaseAccessException,
            NewsFeedGenerationException,
            ResearchProcessingException,
            NotImplementedException,
            InvalidParameterException,
            SchedulerNotificationException,
        )

        exception_classes = [
            NewsFeatureDisabledException,
            InvalidLimitException,
            SubscriptionNotFoundException,
            SubscriptionCreationException,
            SubscriptionUpdateException,
            SubscriptionDeletionException,
            DatabaseAccessException,
            NewsFeedGenerationException,
            ResearchProcessingException,
            NotImplementedException,
            InvalidParameterException,
            SchedulerNotificationException,
        ]

        for exc_class in exception_classes:
            assert issubclass(exc_class, NewsAPIException)
            assert issubclass(exc_class, Exception)


class TestExceptionToDictJsonSerializable:
    """Test that to_dict output is JSON serializable."""

    def test_to_dict_is_json_serializable(self):
        """Test to_dict returns JSON-serializable dict."""
        import json
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException(
            "Test error",
            status_code=400,
            error_code="TEST",
            details={"key": "value", "num": 123, "list": [1, 2, 3]},
        )

        result = exc.to_dict()
        # Should not raise
        json_str = json.dumps(result)
        assert json_str is not None

    def test_all_exceptions_json_serializable(self):
        """Test all exception to_dict outputs are JSON serializable."""
        import json
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
            InvalidLimitException,
            SubscriptionNotFoundException,
            DatabaseAccessException,
            InvalidParameterException,
        )

        exceptions = [
            NewsFeatureDisabledException(),
            InvalidLimitException(0),
            SubscriptionNotFoundException("sub-123"),
            DatabaseAccessException("query", "timeout"),
            InvalidParameterException("param", "value", "error"),
        ]

        for exc in exceptions:
            result = exc.to_dict()
            json_str = json.dumps(result)
            assert json_str is not None
