"""
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):
        """Invalid input raises (caller logs + skips the row)."""
        from local_deep_research.news.api import _format_time_ago

        with pytest.raises(ValueError):
            _format_time_ago("not-a-date")


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)
