"""Tests for web/api.py module - REST API endpoints."""

from unittest.mock import patch, MagicMock
import time

import pytest


@pytest.fixture
def client():
    """Create a test client for the API."""
    from flask import Flask
    from local_deep_research.web.api import api_blueprint

    app = Flask(__name__)
    app.config["TESTING"] = True
    app.config["SECRET_KEY"] = "test-secret-key"

    app.register_blueprint(api_blueprint)

    return app.test_client()


@pytest.fixture
def authenticated_client():
    """Create a test client with authentication mocked."""
    from flask import Flask
    from local_deep_research.web.api import api_blueprint

    app = Flask(__name__)
    app.config["TESTING"] = True
    app.config["SECRET_KEY"] = "test-secret-key"

    app.register_blueprint(api_blueprint)

    with app.test_client() as client:
        with client.session_transaction() as sess:
            sess["username"] = "testuser"
        yield client


class TestHealthCheck:
    """Tests for /api/v1/health endpoint."""

    def test_returns_ok_status(self, client):
        """Should return ok status."""
        response = client.get("/api/v1/health")
        assert response.status_code == 200
        data = response.get_json()
        assert data["status"] == "ok"
        assert data["message"] == "API is running"
        assert "timestamp" in data

    def test_returns_timestamp(self, client):
        """Should return a valid timestamp."""
        response = client.get("/api/v1/health")
        data = response.get_json()
        # Timestamp should be close to current time
        assert abs(data["timestamp"] - time.time()) < 5


class TestApiDocumentation:
    """Tests for /api/v1/ endpoint."""

    def test_returns_api_docs(self, authenticated_client):
        """Should return API documentation."""
        with patch(
            "local_deep_research.web.api.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.api.get_settings_manager"
            ) as mock_settings:
                mock_manager = MagicMock()
                mock_manager.get_setting.side_effect = lambda key, default: {
                    "app.enable_api": True,
                }.get(key, default)
                mock_settings.return_value = mock_manager

                response = authenticated_client.get("/api/v1/")
                assert response.status_code == 200
                data = response.get_json()
                assert data["api_version"] == "v1"
                assert "endpoints" in data
                assert len(data["endpoints"]) >= 1

    def test_lists_available_endpoints(self, authenticated_client):
        """Should list all available endpoints."""
        with patch(
            "local_deep_research.web.api.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.api.get_settings_manager"
            ) as mock_settings:
                mock_manager = MagicMock()
                mock_manager.get_setting.side_effect = lambda key, default: {
                    "app.enable_api": True,
                }.get(key, default)
                mock_settings.return_value = mock_manager

                response = authenticated_client.get("/api/v1/")
                data = response.get_json()

                # Check that key endpoints are documented
                endpoints = data["endpoints"]
                paths = [ep["path"] for ep in endpoints]
                assert "/api/v1/quick_summary" in paths
                assert "/api/v1/generate_report" in paths
                assert "/api/v1/analyze_documents" in paths


class TestApiAccessControl:
    """Tests for API access control decorator."""

    def test_returns_403_when_api_disabled(self, authenticated_client):
        """Should return 403 when API is disabled."""
        with patch(
            "local_deep_research.web.api.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.api.get_settings_manager"
            ) as mock_settings:
                mock_manager = MagicMock()
                mock_manager.get_setting.side_effect = lambda key, default: {
                    "app.enable_api": False,  # API disabled
                }.get(key, default)
                mock_settings.return_value = mock_manager

                response = authenticated_client.get("/api/v1/")
                assert response.status_code == 403
                data = response.get_json()
                assert "disabled" in data["error"].lower()


class TestRateLimiting:
    """Tests for rate limiting functionality."""

    def test_allows_requests_under_limit(self, authenticated_client):
        """Should allow requests under the rate limit."""
        with patch(
            "local_deep_research.web.api.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.api.get_settings_manager"
            ) as mock_settings:
                mock_manager = MagicMock()
                mock_manager.get_setting.side_effect = lambda key, default: {
                    "app.enable_api": True,
                }.get(key, default)
                mock_settings.return_value = mock_manager

                # Make a few requests
                for _ in range(3):
                    response = authenticated_client.get("/api/v1/")
                    assert response.status_code == 200


class TestQuickSummaryQueryTypeValidation:
    """Tests for query type validation in api_quick_summary (PR #2034).

    PR #2034 adds isinstance(query, str) check to reject non-string
    query values with 400 status.
    """

    def _make_request(self, authenticated_client, json_data):
        """Helper to make a quick_summary request with mocked settings."""
        with patch(
            "local_deep_research.web.api.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.api.get_settings_manager"
            ) as mock_settings:
                mock_manager = MagicMock()
                mock_manager.get_setting.side_effect = lambda key, default: {
                    "app.enable_api": True,
                }.get(key, default)
                mock_settings.return_value = mock_manager

                return authenticated_client.post(
                    "/api/v1/quick_summary",
                    json=json_data,
                )

    def test_rejects_integer_query(self, authenticated_client):
        """Integer query returns 400."""
        response = self._make_request(authenticated_client, {"query": 12345})
        assert response.status_code == 400
        data = response.get_json()
        assert "string" in data["error"].lower()

    def test_rejects_list_query(self, authenticated_client):
        """List query returns 400."""
        response = self._make_request(
            authenticated_client, {"query": ["a", "b"]}
        )
        assert response.status_code == 400

    def test_rejects_dict_query(self, authenticated_client):
        """Dict query returns 400."""
        response = self._make_request(
            authenticated_client, {"query": {"nested": "value"}}
        )
        assert response.status_code == 400

    def test_rejects_null_query(self, authenticated_client):
        """None/null query returns 400."""
        response = self._make_request(authenticated_client, {"query": None})
        assert response.status_code == 400
