"""
Extended tests for the news API module.
Tests for API validation, subscription management, and feedback functions.
"""

import pytest
from unittest.mock import MagicMock, patch
from datetime import datetime, timezone, timedelta


class TestApiValidation:
    """Tests for API parameter validation."""

    def test_invalid_limit_raises_exception(self):
        """Test invalid limit raises InvalidLimitException."""
        from local_deep_research.news.api import get_news_feed
        from local_deep_research.news.exceptions import InvalidLimitException

        with pytest.raises(InvalidLimitException):
            get_news_feed(limit=0)

    def test_negative_limit_raises_exception(self):
        """Test negative limit raises InvalidLimitException."""
        from local_deep_research.news.api import get_news_feed
        from local_deep_research.news.exceptions import InvalidLimitException

        with pytest.raises(InvalidLimitException):
            get_news_feed(limit=-5)


class TestGetSubscriptionHelper:
    """Tests for get_subscription helper."""

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_raises_for_not_found(self, mock_session):
        """Test raises SubscriptionNotFoundException when not found."""
        from local_deep_research.news.api import get_subscription
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter_by().first.return_value = None

        with pytest.raises(SubscriptionNotFoundException):
            get_subscription("nonexistent")


class TestGetSubscriptions:
    """Tests for get_subscriptions function."""

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_returns_dict_with_subscriptions_key(self, mock_session):
        """Test returns dict with 'subscriptions' key."""
        from local_deep_research.news.api import get_subscriptions

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter_by().order_by().all.return_value = []

        result = get_subscriptions("user123")
        assert "subscriptions" in result

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_handles_empty_list(self, mock_session):
        """Test handles empty subscription list."""
        from local_deep_research.news.api import get_subscriptions

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter_by().order_by().all.return_value = []

        result = get_subscriptions("user123")
        assert result["subscriptions"] == []


class TestSubmitFeedback:
    """Tests for submit_feedback function."""

    def test_requires_valid_vote(self):
        """Test requires valid vote value."""
        from local_deep_research.news.api import submit_feedback

        # Invalid vote value should raise ValueError
        with pytest.raises(ValueError):
            submit_feedback("card-123", "user1", "invalid")

    def test_requires_valid_up_or_down(self):
        """Test only 'up' or 'down' are valid votes."""
        from local_deep_research.news.api import submit_feedback

        with pytest.raises(ValueError):
            submit_feedback("card-123", "user1", "neutral")


class TestGetVotesForCards:
    """Tests for get_votes_for_cards function."""

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_returns_dict(self, mock_session):
        """Test returns a dictionary."""
        from local_deep_research.news.api import get_votes_for_cards

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter().all.return_value = []

        result = get_votes_for_cards(["card-1", "card-2"], "user1")
        assert isinstance(result, dict)

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_includes_votes_key(self, mock_session):
        """Test includes 'votes' key."""
        from local_deep_research.news.api import get_votes_for_cards

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter().all.return_value = []

        result = get_votes_for_cards(["card-1"], "user1")
        assert "votes" in result

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_empty_cards_returns_empty_votes(self, mock_session):
        """Test empty card list returns empty votes dict."""
        from local_deep_research.news.api import get_votes_for_cards

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db

        result = get_votes_for_cards([], "user1")
        assert result["votes"] == {}


class TestNotImplementedFeatures:
    """Tests for not-yet-implemented features."""

    def test_research_news_item_not_implemented(self):
        """Test research_news_item raises NotImplementedException."""
        from local_deep_research.news.api import research_news_item
        from local_deep_research.news.exceptions import NotImplementedException

        with pytest.raises(NotImplementedException):
            research_news_item("card-123", "detailed")

    def test_save_news_preferences_not_implemented(self):
        """Test save_news_preferences raises NotImplementedException."""
        from local_deep_research.news.api import save_news_preferences
        from local_deep_research.news.exceptions import NotImplementedException

        with pytest.raises(NotImplementedException):
            save_news_preferences("user1", {"theme": "dark"})

    def test_get_news_categories_not_implemented(self):
        """Test get_news_categories raises NotImplementedException."""
        from local_deep_research.news.api import get_news_categories
        from local_deep_research.news.exceptions import NotImplementedException

        with pytest.raises(NotImplementedException):
            get_news_categories()


class TestPrivateFormatTimeAgo:
    """Tests for _format_time_ago internal function."""

    def test_returns_string_for_recent(self):
        """Test returns string for recent timestamp."""
        from local_deep_research.news.api import _format_time_ago

        # Use ISO format string as that's what the function expects
        recent = datetime.now(timezone.utc).isoformat()
        result = _format_time_ago(recent)
        assert isinstance(result, str)

    def test_returns_string_for_old_timestamp(self):
        """Test returns string for older timestamp."""
        from local_deep_research.news.api import _format_time_ago

        old = (datetime.now(timezone.utc) - timedelta(days=5)).isoformat()
        result = _format_time_ago(old)
        assert isinstance(result, str)

    def test_handles_iso_format(self):
        """Test handles ISO format strings."""
        from local_deep_research.news.api import _format_time_ago

        iso_str = "2024-01-15T12:00:00+00:00"
        result = _format_time_ago(iso_str)
        assert isinstance(result, str)

    def test_handles_invalid_input(self):
        """Test handles invalid input gracefully."""
        from local_deep_research.news.api import _format_time_ago

        result = _format_time_ago("not-a-date")
        assert isinstance(result, str)


class TestCreateSubscription:
    """Tests for create_subscription function."""

    def test_function_exists(self):
        """Test create_subscription function exists."""
        from local_deep_research.news.api import create_subscription

        assert callable(create_subscription)


class TestUpdateSubscription:
    """Tests for update_subscription function."""

    def test_function_exists(self):
        """Test update_subscription function exists."""
        from local_deep_research.news.api import update_subscription

        assert callable(update_subscription)


class TestDeleteSubscription:
    """Tests for delete_subscription function."""

    def test_function_exists(self):
        """Test delete_subscription function exists."""
        from local_deep_research.news.api import delete_subscription

        assert callable(delete_subscription)

    @patch("local_deep_research.database.session_context.get_user_db_session")
    def test_raises_for_not_found(self, mock_session):
        """Test raises SubscriptionNotFoundException when not found."""
        from local_deep_research.news.api import delete_subscription
        from local_deep_research.news.exceptions import (
            SubscriptionNotFoundException,
        )

        mock_db = MagicMock()
        mock_session.return_value.__enter__.return_value = mock_db
        mock_db.query().filter_by().first.return_value = None

        with pytest.raises(SubscriptionNotFoundException):
            delete_subscription("nonexistent")


class TestGetSubscriptionHistory:
    """Tests for get_subscription_history function."""

    def test_function_exists(self):
        """Test get_subscription_history function exists."""
        from local_deep_research.news.api import get_subscription_history

        assert callable(get_subscription_history)


class TestGetNewsFeed:
    """Tests for get_news_feed function."""

    def test_function_exists(self):
        """Test get_news_feed function exists."""
        from local_deep_research.news.api import get_news_feed

        assert callable(get_news_feed)

    def test_rejects_zero_limit(self):
        """Test rejects zero limit."""
        from local_deep_research.news.api import get_news_feed
        from local_deep_research.news.exceptions import InvalidLimitException

        with pytest.raises(InvalidLimitException):
            get_news_feed(limit=0)

    def test_rejects_negative_limit(self):
        """Test rejects negative limit."""
        from local_deep_research.news.api import get_news_feed
        from local_deep_research.news.exceptions import InvalidLimitException

        with pytest.raises(InvalidLimitException):
            get_news_feed(limit=-10)


class TestGetRecommender:
    """Tests for get_recommender function."""

    def test_function_exists(self):
        """Test get_recommender function exists."""
        from local_deep_research.news.api import get_recommender

        assert callable(get_recommender)

    def test_returns_topic_based_recommender(self):
        """Test returns TopicBasedRecommender instance."""
        from local_deep_research.news.api import (
            get_recommender,
            TopicBasedRecommender,
        )

        result = get_recommender()
        assert isinstance(result, TopicBasedRecommender)


class TestApiModuleImports:
    """Tests for module-level imports."""

    def test_imports_invalid_limit_exception(self):
        """Test InvalidLimitException is importable."""
        from local_deep_research.news.api import InvalidLimitException

        assert InvalidLimitException is not None

    def test_imports_subscription_not_found_exception(self):
        """Test SubscriptionNotFoundException is importable."""
        from local_deep_research.news.api import SubscriptionNotFoundException

        assert SubscriptionNotFoundException is not None

    def test_imports_not_implemented_exception(self):
        """Test NotImplementedException is importable."""
        from local_deep_research.news.api import NotImplementedException

        assert NotImplementedException is not None

    def test_imports_database_access_exception(self):
        """Test DatabaseAccessException is importable."""
        from local_deep_research.news.api import DatabaseAccessException

        assert DatabaseAccessException is not None
