"""
Tests for the news API routes.

Tests cover:
- News feed retrieval
- Subscription management
- Feedback submission
- News preferences
- Categories
"""

from unittest.mock import patch

import pytest


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

    def test_get_news_feed_success(self, authenticated_client):
        """Get news feed returns feed items."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_news_feed"
        ) as mock_get_feed:
            mock_get_feed.return_value = {"items": [], "total": 0}

            response = authenticated_client.get("/api/news/feed")

            assert response.status_code == 200
            data = response.get_json()
            assert "items" in data

    def test_get_news_feed_with_params(self, authenticated_client):
        """Get news feed with query parameters."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_news_feed"
        ) as mock_get_feed:
            mock_get_feed.return_value = {"items": [], "total": 0}

            response = authenticated_client.get(
                "/api/news/feed?limit=10&use_cache=false&focus=technology"
            )

            assert response.status_code == 200
            mock_get_feed.assert_called_once()
            call_kwargs = mock_get_feed.call_args[1]
            assert call_kwargs["limit"] == 10
            assert call_kwargs["use_cache"] is False
            assert call_kwargs["focus"] == "technology"

    def test_get_news_feed_exception(self, authenticated_client):
        """Get news feed handles exceptions."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_news_feed"
        ) as mock_get_feed:
            mock_get_feed.side_effect = Exception("Database error")

            response = authenticated_client.get("/api/news/feed")

            assert response.status_code == 500
            data = response.get_json()
            assert "error" in data


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

    def test_get_subscriptions_success(self, authenticated_client):
        """Get subscriptions returns list."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_subscriptions"
        ) as mock_get:
            mock_get.return_value = {"subscriptions": []}

            response = authenticated_client.get("/api/news/subscriptions")

            assert response.status_code == 200
            data = response.get_json()
            assert "subscriptions" in data


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

    def test_create_subscription_success(self, authenticated_client):
        """Create subscription succeeds."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.create_subscription"
        ) as mock_create:
            mock_create.return_value = {"id": "sub-123", "query": "Test"}

            response = authenticated_client.post(
                "/api/news/subscriptions",
                json={"query": "Test query", "type": "search"},
                content_type="application/json",
            )

            assert response.status_code == 201
            data = response.get_json()
            assert "id" in data

    def test_create_subscription_with_all_params(self, authenticated_client):
        """Create subscription with all parameters."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.create_subscription"
        ) as mock_create:
            mock_create.return_value = {"id": "sub-123"}

            response = authenticated_client.post(
                "/api/news/subscriptions",
                json={
                    "query": "Test",
                    "type": "search",
                    "refresh_minutes": 60,
                    "model_provider": "ollama",
                    "model": "llama3",
                    "search_strategy": "standard",
                    "name": "My Subscription",
                    "is_active": True,
                    "search_engine": "searxng",
                    "search_iterations": 3,
                    "questions_per_iteration": 2,
                },
                content_type="application/json",
            )

            assert response.status_code == 201
            mock_create.assert_called_once()


class TestGetSubscription:
    """Tests for get_subscription endpoint."""

    def test_get_subscription_success(self, authenticated_client):
        """Get single subscription returns data."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_subscription"
        ) as mock_get:
            mock_get.return_value = {"id": "sub-123", "query": "Test"}

            response = authenticated_client.get(
                "/api/news/subscriptions/sub-123"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["id"] == "sub-123"


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

    def test_update_subscription_put(self, authenticated_client):
        """Update subscription via PUT."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.update_subscription"
        ) as mock_update:
            mock_update.return_value = {"id": "sub-123", "query": "Updated"}

            response = authenticated_client.put(
                "/api/news/subscriptions/sub-123",
                json={"query": "Updated query"},
                content_type="application/json",
            )

            assert response.status_code == 200

    def test_update_subscription_patch(self, authenticated_client):
        """Update subscription via PATCH."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.update_subscription"
        ) as mock_update:
            mock_update.return_value = {"id": "sub-123"}

            response = authenticated_client.patch(
                "/api/news/subscriptions/sub-123",
                json={"is_active": False},
                content_type="application/json",
            )

            assert response.status_code == 200


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

    def test_delete_subscription_success(self, authenticated_client):
        """Delete subscription succeeds."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.delete_subscription"
        ) as mock_delete:
            mock_delete.return_value = {"deleted": True}

            response = authenticated_client.delete(
                "/api/news/subscriptions/sub-123"
            )

            assert response.status_code == 200


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

    def test_get_subscription_history_success(self, authenticated_client):
        """Get subscription history returns history."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_subscription_history"
        ) as mock_get:
            mock_get.return_value = {"history": []}

            response = authenticated_client.get(
                "/api/news/subscriptions/sub-123/history"
            )

            assert response.status_code == 200

    def test_get_subscription_history_with_limit(self, authenticated_client):
        """Get subscription history with limit parameter."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_subscription_history"
        ) as mock_get:
            mock_get.return_value = {"history": []}

            response = authenticated_client.get(
                "/api/news/subscriptions/sub-123/history?limit=10"
            )

            assert response.status_code == 200
            mock_get.assert_called_once_with("sub-123", 10)


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

    def test_submit_feedback_upvote(self, authenticated_client):
        """Submit upvote feedback."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.submit_feedback"
        ) as mock_submit:
            mock_submit.return_value = {"success": True}

            response = authenticated_client.post(
                "/api/news/feedback",
                json={"card_id": "card-123", "vote": "up"},
                content_type="application/json",
            )

            assert response.status_code == 200

    def test_submit_feedback_downvote(self, authenticated_client):
        """Submit downvote feedback."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.submit_feedback"
        ) as mock_submit:
            mock_submit.return_value = {"success": True}

            response = authenticated_client.post(
                "/api/news/feedback",
                json={"card_id": "card-123", "vote": "down"},
                content_type="application/json",
            )

            assert response.status_code == 200

    def test_submit_feedback_invalid_vote(self, authenticated_client):
        """Submit feedback with invalid vote."""
        response = authenticated_client.post(
            "/api/news/feedback",
            json={"card_id": "card-123", "vote": "invalid"},
            content_type="application/json",
        )

        assert response.status_code == 400

    def test_submit_feedback_missing_card_id(self, authenticated_client):
        """Submit feedback without card_id."""
        response = authenticated_client.post(
            "/api/news/feedback",
            json={"vote": "up"},
            content_type="application/json",
        )

        assert response.status_code == 400


class TestResearchNewsItem:
    """Tests for research_news_item endpoint."""

    def test_research_news_item_success(self, authenticated_client):
        """Research news item succeeds."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.research_news_item"
        ) as mock_research:
            mock_research.return_value = {"research_id": "res-123"}

            response = authenticated_client.post(
                "/api/news/research",
                json={"card_id": "card-123"},
                content_type="application/json",
            )

            assert response.status_code == 200

    def test_research_news_item_with_depth(self, authenticated_client):
        """Research news item with custom depth."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.research_news_item"
        ) as mock_research:
            mock_research.return_value = {"research_id": "res-123"}

            response = authenticated_client.post(
                "/api/news/research",
                json={"card_id": "card-123", "depth": "detailed"},
                content_type="application/json",
            )

            assert response.status_code == 200
            mock_research.assert_called_once_with("card-123", "detailed")

    def test_research_news_item_missing_card_id(self, authenticated_client):
        """Research news item without card_id."""
        response = authenticated_client.post(
            "/api/news/research",
            json={},
            content_type="application/json",
        )

        assert response.status_code == 400


class TestSavePreferences:
    """Tests for save_preferences endpoint."""

    def test_save_preferences_success(self, authenticated_client):
        """Save preferences succeeds."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.save_news_preferences"
        ) as mock_save:
            mock_save.return_value = {"saved": True}

            response = authenticated_client.post(
                "/api/news/preferences",
                json={"categories": ["tech", "science"]},
                content_type="application/json",
            )

            assert response.status_code == 200


class TestGetCategories:
    """Tests for get_categories endpoint."""

    def test_get_categories_success(self, authenticated_client):
        """Get categories returns category list."""
        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_news_categories"
        ) as mock_get:
            mock_get.return_value = {
                "categories": [
                    {"name": "Technology", "count": 10},
                    {"name": "Science", "count": 5},
                ]
            }

            response = authenticated_client.get("/api/news/categories")

            assert response.status_code == 200
            data = response.get_json()
            assert "categories" in data


class TestNewsAPIExceptionHandler:
    """Tests for NewsAPIException error handler."""

    def test_news_api_exception_handled(self, authenticated_client):
        """NewsAPIException is handled properly."""
        from local_deep_research.news.exceptions import NewsAPIException

        with patch(
            "local_deep_research.web.routes.news_routes.news_api.get_news_feed"
        ) as mock_get:
            mock_get.side_effect = NewsAPIException(
                message="Test error",
                error_code="TEST_ERROR",
                status_code=400,
            )

            response = authenticated_client.get("/api/news/feed")

            assert response.status_code == 400
            data = response.get_json()
            assert data["error_code"] == "TEST_ERROR"


class TestHandleApiErrorsDecorator:
    """Tests for handle_api_errors decorator (PR #1996).

    The decorator catches generic exceptions and returns 500,
    but re-raises NewsAPIException for the error handler.
    """

    def test_decorator_returns_500_on_generic_exception(self):
        """Generic exceptions return 500 JSON response."""
        from flask import Flask

        from local_deep_research.web.routes.news_routes import (
            handle_api_errors,
        )

        app = Flask(__name__)
        app.config["TESTING"] = True

        with app.app_context():

            @handle_api_errors
            def failing_view():
                raise RuntimeError("Unexpected error")

            response, status = failing_view()
            assert status == 500
            data = response.get_json()
            assert "error" in data

    def test_decorator_reraises_news_api_exception(self):
        """NewsAPIException is re-raised, not caught."""
        from local_deep_research.news.exceptions import NewsAPIException
        from local_deep_research.web.routes.news_routes import (
            handle_api_errors,
        )

        @handle_api_errors
        def news_error_view():
            raise NewsAPIException("Test news error")

        with pytest.raises(NewsAPIException):
            news_error_view()

    def test_decorator_passes_through_on_success(self):
        """Successful function result is passed through."""
        from flask import Flask, jsonify

        from local_deep_research.web.routes.news_routes import (
            handle_api_errors,
        )

        app = Flask(__name__)
        app.config["TESTING"] = True

        with app.app_context():

            @handle_api_errors
            def ok_view():
                return jsonify({"status": "ok"}), 200

            response, status = ok_view()
            assert status == 200

    def test_decorator_preserves_function_name(self):
        """Decorator preserves wrapped function name."""
        from local_deep_research.web.routes.news_routes import (
            handle_api_errors,
        )

        @handle_api_errors
        def my_news_endpoint():
            pass

        assert my_news_endpoint.__name__ == "my_news_endpoint"
