# allow: no-sut-import — black-box HTTP test; drives real routes through the Flask test client
"""Tests for api_routes module - API endpoints."""

from unittest.mock import patch, MagicMock

# API routes are registered under /research/api prefix
API_PREFIX = "/research/api"


class TestGetCurrentConfig:
    """Tests for /settings/current-config endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.get(f"{API_PREFIX}/settings/current-config")
        # Should redirect to login or return 401
        assert response.status_code == 401, response.status_code

    def test_returns_config_when_authenticated(self, authenticated_client):
        """Should return config when authenticated."""
        with patch(
            "local_deep_research.web.routes.api_routes.get_user_db_session"
        ) as mock_session_ctx:
            mock_session = MagicMock()
            mock_session_ctx.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_session_ctx.return_value.__exit__ = MagicMock(
                return_value=None
            )

            with patch(
                "local_deep_research.web.routes.api_routes.SettingsManager"
            ) as mock_sm:
                mock_instance = MagicMock()
                mock_instance.get_setting.side_effect = lambda key, default: {
                    "llm.provider": "ollama",
                    "llm.model": "llama3",
                    "search.tool": "searxng",
                    "search.iterations": 8,
                    "search.questions_per_iteration": 5,
                    "search.search_strategy": "focused_iteration",
                }.get(key, default)
                mock_sm.return_value = mock_instance

                response = authenticated_client.get(
                    f"{API_PREFIX}/settings/current-config"
                )

                assert response.status_code == 200
                data = response.get_json()
                assert data["success"] is True
                assert "config" in data
                assert data["config"]["provider"] == "ollama"


class TestApiStartResearch:
    """Tests for /start endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.post(
            f"{API_PREFIX}/start", json={"query": "test query", "mode": "quick"}
        )
        assert response.status_code == 401, response.status_code

    def test_requires_query(self, authenticated_client):
        """Should require query parameter."""
        response = authenticated_client.post(
            f"{API_PREFIX}/start", json={"mode": "quick"}
        )
        assert response.status_code == 400
        data = response.get_json()
        assert "Query is required" in data.get("message", "")

    def test_empty_query_rejected(self, authenticated_client):
        """Should reject empty query."""
        response = authenticated_client.post(
            f"{API_PREFIX}/start", json={"query": "", "mode": "quick"}
        )
        assert response.status_code == 400

    def test_starts_research_successfully(self, authenticated_client):
        """Should delegate to start_research and return success."""
        with patch(
            "local_deep_research.web.routes.research_routes.start_research"
        ) as mock_start:
            mock_start.return_value = (
                authenticated_client.application.make_response(
                    (
                        '{"status": "success", "research_id": "test-uuid"}',
                        200,
                        {"Content-Type": "application/json"},
                    )
                )
            )

            response = authenticated_client.post(
                f"{API_PREFIX}/start",
                json={"query": "What is AI?", "mode": "quick"},
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "success"
            assert data["research_id"] == "test-uuid"
            mock_start.assert_called_once()

    def test_starts_research_queued(self, authenticated_client):
        """Should delegate to start_research and return queued status."""
        with patch(
            "local_deep_research.web.routes.research_routes.start_research"
        ) as mock_start:
            mock_start.return_value = authenticated_client.application.make_response(
                (
                    '{"status": "queued", "research_id": "test-uuid", "queue_position": 1}',
                    200,
                    {"Content-Type": "application/json"},
                )
            )

            response = authenticated_client.post(
                f"{API_PREFIX}/start",
                json={"query": "What is AI?", "mode": "quick"},
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "queued"
            assert data["research_id"] == "test-uuid"
            mock_start.assert_called_once()


class TestApiResearchStatus:
    """Tests for /status/<research_id> endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.get(f"{API_PREFIX}/status/test-id")
        assert response.status_code == 401, response.status_code

    def test_returns_404_for_nonexistent(self, authenticated_client):
        """Should return 404 for non-existent research."""
        with patch(
            "local_deep_research.web.routes.api_routes.get_user_db_session"
        ) as mock_session_ctx:
            mock_session = MagicMock()
            mock_session_ctx.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_session_ctx.return_value.__exit__ = MagicMock(
                return_value=None
            )

            mock_query = MagicMock()
            mock_query.filter_by.return_value.first.return_value = None
            mock_session.query.return_value = mock_query

            response = authenticated_client.get(
                f"{API_PREFIX}/status/nonexistent-id"
            )
            assert response.status_code == 404

    def test_returns_status_for_existing(self, authenticated_client):
        """Should return status for existing research."""
        with patch(
            "local_deep_research.web.routes.api_routes.get_user_db_session"
        ) as mock_session_ctx:
            mock_session = MagicMock()
            mock_session_ctx.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_session_ctx.return_value.__exit__ = MagicMock(
                return_value=None
            )

            mock_research = MagicMock()
            mock_research.status = "completed"
            mock_research.progress = 100
            mock_research.completed_at = "2024-01-01T00:00:00"
            mock_research.report_path = "/path/to/report.md"
            mock_research.research_meta = {"key": "value"}

            mock_query = MagicMock()
            mock_query.filter_by.return_value.first.return_value = mock_research
            mock_session.query.return_value = mock_query

            response = authenticated_client.get(
                f"{API_PREFIX}/status/existing-id"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "completed"
            assert data["progress"] == 100


class TestApiTerminateResearch:
    """Tests for /terminate/<research_id> endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.post(f"{API_PREFIX}/terminate/test-id")
        assert response.status_code == 401, response.status_code

    def test_terminates_research(self, authenticated_client):
        """Should terminate research."""
        with patch(
            "local_deep_research.web.routes.api_routes.cancel_research"
        ) as mock_cancel:
            mock_cancel.return_value = True

            response = authenticated_client.post(
                f"{API_PREFIX}/terminate/test-id"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "success"

    def test_handles_not_found(self, authenticated_client):
        """Should handle research not found."""
        with patch(
            "local_deep_research.web.routes.api_routes.cancel_research"
        ) as mock_cancel:
            mock_cancel.return_value = False

            response = authenticated_client.post(
                f"{API_PREFIX}/terminate/nonexistent"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert "not found or already completed" in data["message"]


class TestApiGetResources:
    """Tests for GET /resources/<research_id> endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.get(f"{API_PREFIX}/resources/test-id")
        assert response.status_code == 401, response.status_code

    def test_returns_resources(self, authenticated_client):
        """Should return resources for research."""
        with patch(
            "local_deep_research.web.routes.api_routes.get_resources_for_research"
        ) as mock_get:
            mock_get.return_value = [
                {"id": 1, "title": "Resource 1", "url": "https://example.com"}
            ]

            response = authenticated_client.get(
                f"{API_PREFIX}/resources/test-id"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "success"
            assert len(data["resources"]) == 1


class TestApiAddResource:
    """Tests for POST /resources/<research_id> endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.post(
            f"{API_PREFIX}/resources/test-id",
            json={"title": "Test", "url": "https://example.com"},
        )
        assert response.status_code == 401, response.status_code

    def test_requires_title_and_url(self, authenticated_client):
        """Should require both title and URL."""
        response = authenticated_client.post(
            f"{API_PREFIX}/resources/test-id", json={"title": "Test only"}
        )
        assert response.status_code == 400

        response = authenticated_client.post(
            f"{API_PREFIX}/resources/test-id",
            json={"url": "https://example.com"},
        )
        assert response.status_code == 400

    def test_returns_404_for_nonexistent_research(self, authenticated_client):
        """Should return 404 if research doesn't exist."""
        with (
            patch(
                "local_deep_research.web.routes.api_routes.get_user_db_session"
            ) as mock_session_ctx,
            patch(
                "local_deep_research.security.ssrf_validator.validate_url",
                return_value=(True, None),
            ),
        ):
            mock_session = MagicMock()
            mock_session_ctx.return_value.__enter__ = MagicMock(
                return_value=mock_session
            )
            mock_session_ctx.return_value.__exit__ = MagicMock(
                return_value=None
            )

            mock_query = MagicMock()
            mock_query.filter_by.return_value.first.return_value = None
            mock_session.query.return_value = mock_query

            response = authenticated_client.post(
                f"{API_PREFIX}/resources/nonexistent",
                json={"title": "Test", "url": "https://example.com"},
            )

            assert response.status_code == 404


class TestApiDeleteResource:
    """Tests for DELETE /resources/<research_id>/delete/<resource_id> endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.delete(f"{API_PREFIX}/resources/test-id/delete/1")
        assert response.status_code == 401, response.status_code

    def test_deletes_resource(self, authenticated_client):
        """Should delete resource successfully."""
        with patch(
            "local_deep_research.web.routes.api_routes.delete_resource"
        ) as mock_delete:
            mock_delete.return_value = True

            response = authenticated_client.delete(
                f"{API_PREFIX}/resources/test-id/delete/1"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["status"] == "success"

    def test_returns_404_for_nonexistent(self, authenticated_client):
        """Should return 404 for non-existent resource."""
        with patch(
            "local_deep_research.web.routes.api_routes.delete_resource"
        ) as mock_delete:
            mock_delete.return_value = False

            response = authenticated_client.delete(
                f"{API_PREFIX}/resources/test-id/delete/999"
            )

            assert response.status_code == 404


class TestCheckOllamaStatus:
    """Tests for /check/ollama_status endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.get(f"{API_PREFIX}/check/ollama_status")
        assert response.status_code == 401, response.status_code

    def test_non_ollama_provider(self, authenticated_client, app):
        """Should return running=True for non-Ollama providers."""
        app.config["LLM_CONFIG"] = {"provider": "openai"}

        response = authenticated_client.get(f"{API_PREFIX}/check/ollama_status")

        assert response.status_code == 200
        data = response.get_json()
        assert data["running"] is True
        assert "openai" in data["message"].lower()

    def test_ollama_connection_error(self, authenticated_client, app):
        """Should return running=False on connection error."""
        import requests

        app.config["LLM_CONFIG"] = {
            "provider": "ollama",
            "ollama_base_url": "http://localhost:11434",
        }

        with patch(
            "local_deep_research.web.routes.api_routes.safe_get",
            side_effect=requests.exceptions.ConnectionError(
                "Connection refused"
            ),
        ):
            response = authenticated_client.get(
                f"{API_PREFIX}/check/ollama_status"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["running"] is False
            assert "error_type" in data

    def test_ollama_running(self, authenticated_client, app):
        """Should return running=True when Ollama is running."""
        app.config["LLM_CONFIG"] = {
            "provider": "ollama",
            "ollama_base_url": "http://localhost:11434",
        }

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"models": [{"name": "llama3"}]}

        with patch(
            "local_deep_research.web.routes.api_routes.safe_get",
            return_value=mock_response,
        ):
            response = authenticated_client.get(
                f"{API_PREFIX}/check/ollama_status"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["running"] is True
            assert data["model_count"] == 1


class TestCheckOllamaModel:
    """Tests for /check/ollama_model endpoint."""

    def test_requires_authentication(self, client):
        """Should require authentication."""
        response = client.get(f"{API_PREFIX}/check/ollama_model")
        assert response.status_code == 401, response.status_code

    def test_non_ollama_provider(self, authenticated_client, app):
        """Should return available=True for non-Ollama providers."""
        app.config["LLM_CONFIG"] = {"provider": "openai"}

        response = authenticated_client.get(f"{API_PREFIX}/check/ollama_model")

        assert response.status_code == 200
        data = response.get_json()
        assert data["available"] is True
        assert data["provider"] == "openai"

    def test_model_available(self, authenticated_client, app):
        """Should return available=True when model exists."""
        app.config["LLM_CONFIG"] = {
            "provider": "ollama",
            "model": "llama3",
            "ollama_base_url": "http://localhost:11434",
        }

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"models": [{"name": "llama3"}]}

        with patch(
            "local_deep_research.web.routes.api_routes.safe_get",
            return_value=mock_response,
        ):
            response = authenticated_client.get(
                f"{API_PREFIX}/check/ollama_model"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["available"] is True
            assert data["model"] == "llama3"

    def test_model_not_available(self, authenticated_client, app):
        """Should return available=False when model doesn't exist."""
        app.config["LLM_CONFIG"] = {
            "provider": "ollama",
            "model": "nonexistent-model",
            "ollama_base_url": "http://localhost:11434",
        }

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"models": [{"name": "llama3"}]}

        with patch(
            "local_deep_research.web.routes.api_routes.safe_get",
            return_value=mock_response,
        ):
            response = authenticated_client.get(
                f"{API_PREFIX}/check/ollama_model"
            )

            assert response.status_code == 200
            data = response.get_json()
            assert data["available"] is False
