"""
Coverage gap tests for settings_routes.py targeting remaining uncovered paths.

Covers:
- get_bulk_settings: default keys, custom keys, error handling
- api_get_data_location: platform info, encryption status
- api_test_notification_url: success, missing URL, exception
- api_get_available_models: cache hit, force refresh, provider discovery
"""

from contextlib import contextmanager
from unittest.mock import MagicMock, Mock, patch

from flask import Flask, jsonify

from local_deep_research.web.auth.routes import auth_bp
from local_deep_research.web.routes.settings_routes import settings_bp

MODULE = "local_deep_research.web.routes.settings_routes"
DECORATOR_MODULE = "local_deep_research.web.utils.route_decorators"


def _create_test_app():
    app = Flask(__name__)
    app.config["SECRET_KEY"] = "test-secret"
    app.config["WTF_CSRF_ENABLED"] = False
    app.register_blueprint(auth_bp)
    app.register_blueprint(settings_bp)

    @app.errorhandler(500)
    def _handle_500(error):
        return jsonify({"error": "Internal server error"}), 500

    return app


@contextmanager
def _authenticated_client(app):
    mock_db = Mock()
    mock_db.connections = {"testuser": True}
    mock_db.has_encryption = False

    @contextmanager
    def _fake_session(*args, **kwargs):
        yield MagicMock()

    patches = [
        patch("local_deep_research.web.auth.decorators.db_manager", mock_db),
        patch(
            f"{DECORATOR_MODULE}.get_user_db_session", side_effect=_fake_session
        ),
        patch(f"{MODULE}.settings_limit", lambda f: f),
    ]

    started = []
    try:
        for p in patches:
            started.append(p.start())
        with app.test_client() as client:
            with client.session_transaction() as sess:
                sess["username"] = "testuser"
                sess["session_id"] = "test-session-id"
            yield client
    finally:
        for p in patches:
            p.stop()


# ===========================================================================
# get_bulk_settings
# ===========================================================================


class TestGetBulkSettings:
    """Tests for GET /settings/api/bulk endpoint."""

    def test_returns_defaults_when_no_keys_specified(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            with patch(
                f"{MODULE}._get_setting_from_session", return_value="test-val"
            ):
                resp = client.get("/settings/api/bulk")
                assert resp.status_code == 200
                data = resp.get_json()
                assert data["success"] is True
                # Default keys should include llm.provider, llm.model, etc.
                assert "llm.provider" in data["settings"]
                assert "search.tool" in data["settings"]

    def test_returns_specific_keys(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            with patch(
                f"{MODULE}._get_setting_from_session", return_value="val"
            ):
                resp = client.get(
                    "/settings/api/bulk?keys[]=custom.key1&keys[]=custom.key2"
                )
                assert resp.status_code == 200
                data = resp.get_json()
                assert "custom.key1" in data["settings"]
                assert "custom.key2" in data["settings"]
                assert data["settings"]["custom.key1"]["value"] == "val"

    def test_returns_exists_false_for_none_value(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            with patch(
                f"{MODULE}._get_setting_from_session", return_value=None
            ):
                resp = client.get("/settings/api/bulk?keys[]=missing.key")
                data = resp.get_json()
                assert data["settings"]["missing.key"]["exists"] is False

    def test_handles_per_key_errors(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            with patch(
                f"{MODULE}._get_setting_from_session",
                side_effect=RuntimeError("DB error"),
            ):
                resp = client.get("/settings/api/bulk?keys[]=bad.key")
                data = resp.get_json()
                assert data["success"] is True
                assert data["settings"]["bad.key"]["exists"] is False
                assert "error" in data["settings"]["bad.key"]


# ===========================================================================
# api_get_data_location
# ===========================================================================


class TestApiGetDataLocation:
    """Tests for GET /settings/api/data-location endpoint."""

    def test_returns_data_location_info(self):
        app = _create_test_app()
        mock_sm = Mock()
        mock_sm.get_setting.return_value = None  # No custom data dir

        with _authenticated_client(app) as client:
            with (
                patch(f"{MODULE}.get_data_directory", return_value="/data/ldr"),
                patch(
                    f"{MODULE}.get_encrypted_database_path",
                    return_value="/data/ldr/encrypted",
                ),
                patch(f"{MODULE}.db_manager", Mock(has_encryption=True)),
                patch(
                    "local_deep_research.settings.manager.SettingsManager",
                    return_value=mock_sm,
                ),
                patch(f"{MODULE}.get_user_db_session"),
                patch(
                    "local_deep_research.database.sqlcipher_utils.get_sqlcipher_settings",
                    return_value={"kdf_iterations": 256000},
                ),
            ):
                resp = client.get("/settings/api/data-location")
                assert resp.status_code == 200
                data = resp.get_json()
                assert data["data_directory"] == "/data/ldr"
                assert data["is_custom"] is False
                assert data["security_notice"]["encrypted"] is True

    def test_returns_unencrypted_warning(self):
        app = _create_test_app()
        mock_sm = Mock()
        mock_sm.get_setting.return_value = "/custom/dir"

        with _authenticated_client(app) as client:
            with (
                patch(f"{MODULE}.get_data_directory", return_value="/data/ldr"),
                patch(
                    f"{MODULE}.get_encrypted_database_path",
                    return_value="/data/ldr/db",
                ),
                patch(f"{MODULE}.db_manager", Mock(has_encryption=False)),
                patch(
                    "local_deep_research.settings.manager.SettingsManager",
                    return_value=mock_sm,
                ),
                patch(f"{MODULE}.get_user_db_session"),
            ):
                resp = client.get("/settings/api/data-location")
                data = resp.get_json()
                assert data["is_custom"] is True
                assert data["security_notice"]["encrypted"] is False

    def test_exception_returns_500(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            with patch(
                f"{MODULE}.get_data_directory", side_effect=RuntimeError("fail")
            ):
                resp = client.get("/settings/api/data-location")
                assert resp.status_code == 500


# ===========================================================================
# api_test_notification_url
# ===========================================================================


class TestApiTestNotificationUrl:
    """Tests for POST /settings/api/notifications/test-url endpoint."""

    def test_success(self):
        app = _create_test_app()
        mock_svc = Mock()
        mock_svc.test_service.return_value = {
            "success": True,
            "message": "Notification sent",
        }

        with _authenticated_client(app) as client:
            with patch(
                "local_deep_research.notifications.service.NotificationService",
                return_value=mock_svc,
            ):
                resp = client.post(
                    "/settings/api/notifications/test-url",
                    json={"service_url": "ntfy://topic"},
                    content_type="application/json",
                )
                assert resp.status_code == 200
                data = resp.get_json()
                assert data["success"] is True

    def test_missing_service_url_returns_400(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            resp = client.post(
                "/settings/api/notifications/test-url",
                json={"wrong_key": "value"},
                content_type="application/json",
            )
            assert resp.status_code == 400
            data = resp.get_json()
            assert data["success"] is False

    def test_empty_body_returns_400(self):
        app = _create_test_app()
        with _authenticated_client(app) as client:
            resp = client.post(
                "/settings/api/notifications/test-url",
                json={},
                content_type="application/json",
            )
            assert resp.status_code == 400


# ===========================================================================
# api_get_available_models — cache path
# ===========================================================================


class TestApiGetAvailableModels:
    """Tests for GET /settings/api/available-models endpoint."""

    def test_force_refresh_bypasses_cache(self):
        """force_refresh=true skips cache and fetches live."""
        app = _create_test_app()

        @contextmanager
        def _fake_session(*a, **kw):
            yield MagicMock()

        with _authenticated_client(app) as client:
            with (
                patch(
                    f"{MODULE}.get_user_db_session", side_effect=_fake_session
                ),
                patch(
                    "local_deep_research.llm.providers.get_discovered_provider_options",
                    return_value=[],
                ),
                patch(f"{MODULE}.safe_get", return_value=Mock(status_code=404)),
                patch(f"{MODULE}._get_setting_from_session", return_value=None),
            ):
                resp = client.get(
                    "/settings/api/available-models?force_refresh=true"
                )
                assert resp.status_code == 200
                data = resp.get_json()
                assert "providers" in data
