"""
Extended tests for news/exceptions.py

Tests cover:
- NewsAPIException base class
- All derived exception classes
- to_dict() serialization
- Error codes and status codes
- Exception inheritance
"""


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

    def test_inherits_from_exception(self):
        """NewsAPIException inherits from Exception."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test")
        assert isinstance(exc, Exception)

    def test_stores_message(self):
        """Stores the message."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Error message")
        assert exc.message == "Error message"

    def test_default_status_code_is_500(self):
        """Default status_code is 500."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test")
        assert exc.status_code == 500

    def test_custom_status_code(self):
        """Accepts custom status_code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test", status_code=404)
        assert exc.status_code == 404

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

        exc = NewsAPIException("test")
        assert exc.error_code == "NewsAPIException"

    def test_custom_error_code(self):
        """Accepts custom error_code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test", error_code="CUSTOM_ERROR")
        assert exc.error_code == "CUSTOM_ERROR"

    def test_details_defaults_to_empty_dict(self):
        """Details defaults to empty dict."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test")
        assert exc.details == {}

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

        exc = NewsAPIException("test", details={"key": "value"})
        assert exc.details == {"key": "value"}

    def test_str_returns_message(self):
        """str() returns the message."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Error occurred")
        assert str(exc) == "Error occurred"


class TestNewsAPIExceptionToDict:
    """Tests for NewsAPIException.to_dict()."""

    def test_includes_error(self):
        """Dict includes error message."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("Error message")
        data = exc.to_dict()

        assert data["error"] == "Error message"

    def test_includes_error_code(self):
        """Dict includes error_code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test", error_code="TEST_ERROR")
        data = exc.to_dict()

        assert data["error_code"] == "TEST_ERROR"

    def test_includes_status_code(self):
        """Dict includes status_code."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test", status_code=400)
        data = exc.to_dict()

        assert data["status_code"] == 400

    def test_includes_details_when_present(self):
        """Dict includes details when provided."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test", details={"field": "value"})
        data = exc.to_dict()

        assert data["details"] == {"field": "value"}

    def test_excludes_details_when_empty(self):
        """Dict excludes details when empty."""
        from local_deep_research.news.exceptions import NewsAPIException

        exc = NewsAPIException("test")
        data = exc.to_dict()

        assert "details" not in data


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
            NewsAPIException,
        )

        exc = NewsFeatureDisabledException()
        assert isinstance(exc, NewsAPIException)

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

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

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

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

    def test_status_code_is_503(self):
        """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_is_news_disabled(self):
        """Error code is NEWS_DISABLED."""
        from local_deep_research.news.exceptions import (
            NewsFeatureDisabledException,
        )

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


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            InvalidLimitException,
            NewsAPIException,
        )

        exc = InvalidLimitException(0)
        assert isinstance(exc, NewsAPIException)

    def test_includes_limit_in_message(self):
        """Message includes the invalid limit value."""
        from local_deep_research.news.exceptions import InvalidLimitException

        exc = InvalidLimitException(-5)
        assert "-5" in exc.message

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

        exc = InvalidLimitException(0)
        assert exc.status_code == 400

    def test_error_code_is_invalid_limit(self):
        """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):
        """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):
        """Details include min_limit."""
        from local_deep_research.news.exceptions import InvalidLimitException

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


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
            NewsAPIException,
        )

        exc = SubscriptionNotFoundException("sub-123")
        assert isinstance(exc, NewsAPIException)

    def test_includes_subscription_id_in_message(self):
        """Message includes the subscription ID."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

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

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

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

    def test_error_code_is_subscription_not_found(self):
        """Error code is SUBSCRIPTION_NOT_FOUND."""
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

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

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

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


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
            NewsAPIException,
        )

        exc = SubscriptionCreationException("Error")
        assert isinstance(exc, NewsAPIException)

    def test_includes_message_in_error(self):
        """Message includes the provided message."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException("Invalid data")
        assert "Invalid data" in exc.message

    def test_status_code_is_500(self):
        """Status code is 500 (Internal Server Error)."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

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

    def test_error_code_is_subscription_create_failed(self):
        """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_accepts_details(self):
        """Accepts optional details."""
        from local_deep_research.news.exceptions import (
            SubscriptionCreationException,
        )

        exc = SubscriptionCreationException("Error", details={"field": "topic"})
        assert exc.details["field"] == "topic"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
            NewsAPIException,
        )

        exc = SubscriptionUpdateException("sub-1", "Error")
        assert isinstance(exc, NewsAPIException)

    def test_includes_subscription_id_in_message(self):
        """Message includes the subscription ID."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-123", "Update failed")
        assert "sub-123" in exc.message

    def test_includes_error_message(self):
        """Message includes the error message."""
        from local_deep_research.news.exceptions import (
            SubscriptionUpdateException,
        )

        exc = SubscriptionUpdateException("sub-1", "Validation error")
        assert "Validation error" in exc.message

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

        exc = SubscriptionUpdateException("sub-1", "Error")
        assert exc.status_code == 500

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

        exc = SubscriptionUpdateException("sub-xyz", "Error")
        assert exc.details["subscription_id"] == "sub-xyz"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
            NewsAPIException,
        )

        exc = SubscriptionDeletionException("sub-1", "Error")
        assert isinstance(exc, NewsAPIException)

    def test_includes_subscription_id_in_message(self):
        """Message includes the subscription ID."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
        )

        exc = SubscriptionDeletionException("sub-456", "Delete failed")
        assert "sub-456" in exc.message

    def test_error_code_is_subscription_delete_failed(self):
        """Error code is SUBSCRIPTION_DELETE_FAILED."""
        from local_deep_research.news.exceptions import (
            SubscriptionDeletionException,
        )

        exc = SubscriptionDeletionException("sub-1", "Error")
        assert exc.error_code == "SUBSCRIPTION_DELETE_FAILED"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            DatabaseAccessException,
            NewsAPIException,
        )

        exc = DatabaseAccessException("query", "Connection failed")
        assert isinstance(exc, NewsAPIException)

    def test_includes_operation_in_message(self):
        """Message includes the operation."""
        from local_deep_research.news.exceptions import DatabaseAccessException

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

    def test_includes_error_message(self):
        """Message includes the error message."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("op", "Connection timeout")
        assert "Connection timeout" in exc.message

    def test_error_code_is_database_error(self):
        """Error code is DATABASE_ERROR."""
        from local_deep_research.news.exceptions import DatabaseAccessException

        exc = DatabaseAccessException("op", "Error")
        assert exc.error_code == "DATABASE_ERROR"

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

        exc = DatabaseAccessException("update", "Error")
        assert exc.details["operation"] == "update"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
            NewsAPIException,
        )

        exc = NewsFeedGenerationException("Error")
        assert isinstance(exc, NewsAPIException)

    def test_includes_message_in_error(self):
        """Message includes the provided message."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

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

    def test_error_code_is_feed_generation_failed(self):
        """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_includes_user_id_when_provided(self):
        """Details include user_id when provided."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("Error", user_id="user-123")
        assert exc.details["user_id"] == "user-123"

    def test_no_user_id_when_not_provided(self):
        """Details empty when user_id not provided."""
        from local_deep_research.news.exceptions import (
            NewsFeedGenerationException,
        )

        exc = NewsFeedGenerationException("Error")
        assert "user_id" not in exc.details


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
            NewsAPIException,
        )

        exc = ResearchProcessingException("Error")
        assert isinstance(exc, NewsAPIException)

    def test_error_code_is_research_processing_failed(self):
        """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_includes_research_id_when_provided(self):
        """Details include research_id when provided."""
        from local_deep_research.news.exceptions import (
            ResearchProcessingException,
        )

        exc = ResearchProcessingException("Error", research_id="res-123")
        assert exc.details["research_id"] == "res-123"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            NotImplementedException,
            NewsAPIException,
        )

        exc = NotImplementedException("feature")
        assert isinstance(exc, NewsAPIException)

    def test_includes_feature_in_message(self):
        """Message includes the feature name."""
        from local_deep_research.news.exceptions import NotImplementedException

        exc = NotImplementedException("AI summarization")
        assert "AI summarization" in exc.message

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

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

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

        exc = NotImplementedException("real-time updates")
        assert exc.details["feature"] == "real-time updates"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
            NewsAPIException,
        )

        exc = InvalidParameterException("param", "value", "message")
        assert isinstance(exc, NewsAPIException)

    def test_includes_parameter_in_message(self):
        """Message includes the parameter name."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("limit", -1, "Must be positive")
        assert "limit" in exc.message

    def test_includes_error_message(self):
        """Message includes the error description."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("limit", -1, "Must be positive")
        assert "Must be positive" in exc.message

    def test_status_code_is_400(self):
        """Status code is 400 (Bad Request)."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("p", "v", "m")
        assert exc.status_code == 400

    def test_details_include_parameter(self):
        """Details include parameter name."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("offset", "abc", "Must be integer")
        assert exc.details["parameter"] == "offset"

    def test_details_include_value(self):
        """Details include the invalid value."""
        from local_deep_research.news.exceptions import (
            InvalidParameterException,
        )

        exc = InvalidParameterException("offset", "abc", "Must be integer")
        assert exc.details["value"] == "abc"


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

    def test_inherits_from_news_api_exception(self):
        """Inherits from NewsAPIException."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
            NewsAPIException,
        )

        exc = SchedulerNotificationException("action", "Error")
        assert isinstance(exc, NewsAPIException)

    def test_includes_action_in_message(self):
        """Message includes the action."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("subscription_create", "Failed")
        assert "subscription_create" in exc.message

    def test_error_code_is_scheduler_notification_failed(self):
        """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):
        """Details include action."""
        from local_deep_research.news.exceptions import (
            SchedulerNotificationException,
        )

        exc = SchedulerNotificationException("user_update", "Error")
        assert exc.details["action"] == "user_update"
