"""
Deep coverage tests for utilities/db_utils.py.

Targets uncovered paths:
- get_db_session: Flask session username lookup, exception in Flask context
- get_settings_manager: request context username extraction
- get_setting_from_db_main_thread: successful session + settings read
"""

import threading
from unittest.mock import Mock, MagicMock, patch


class TestGetDbSessionFlaskSessionUsername:
    """Cover the Flask session username lookup path (lines 72-76)."""

    def test_returns_session_from_flask_session_username(self):
        """When g.db_session is missing, falls back to flask_session username."""
        from local_deep_research.utilities.db_utils import get_db_session

        mock_session = Mock()
        mock_g = Mock(spec=[])  # g without db_session attribute

        with patch(
            "local_deep_research.utilities.db_utils.has_app_context",
            return_value=True,
        ):
            with patch("local_deep_research.utilities.db_utils.g", mock_g):
                with patch(
                    "local_deep_research.utilities.db_utils.flask_session",
                    {"username": "testuser"},
                ):
                    with patch(
                        "local_deep_research.utilities.db_utils.db_manager"
                    ) as mock_mgr:
                        mock_mgr.get_session.return_value = mock_session
                        # Use unique _namespace to avoid cache hits from other tests
                        result = get_db_session(_namespace="test_flask_session")

                        assert result == mock_session
                        mock_mgr.get_session.assert_called_with("testuser")

    def test_returns_none_when_flask_session_user_has_no_db(self):
        """When flask_session has username but db_manager returns None, falls through."""
        from local_deep_research.utilities.db_utils import get_db_session

        mock_g = Mock(spec=[])  # No db_session attribute

        with patch(
            "local_deep_research.utilities.db_utils.has_app_context",
            return_value=True,
        ):
            with patch("local_deep_research.utilities.db_utils.g", mock_g):
                with patch(
                    "local_deep_research.utilities.db_utils.flask_session",
                    {"username": "nodbuser"},
                ):
                    with patch(
                        "local_deep_research.utilities.db_utils.db_manager"
                    ) as mock_mgr:
                        mock_mgr.get_session.return_value = None
                        # Use unique _namespace to avoid cache hits
                        result = get_db_session(_namespace="test_no_db_user")
                        # Falls through to the warning/None return
                        assert result is None

    def test_returns_none_when_no_username_in_flask_session(self):
        """When flask_session has no username, falls through to None."""
        from local_deep_research.utilities.db_utils import get_db_session

        mock_g = Mock(spec=[])  # No db_session

        with patch(
            "local_deep_research.utilities.db_utils.has_app_context",
            return_value=True,
        ):
            with patch("local_deep_research.utilities.db_utils.g", mock_g):
                with patch(
                    "local_deep_research.utilities.db_utils.flask_session",
                    {},  # No username in session
                ):
                    result = get_db_session(
                        _namespace="test_no_username_session"
                    )
                    assert result is None


class TestGetSettingsManagerRequestContext:
    """Cover get_settings_manager with request context username extraction."""

    def test_extracts_username_from_flask_session(self):
        """When no args given and in request context, extracts username from session."""
        from local_deep_research.utilities.db_utils import get_settings_manager

        mock_session_obj = Mock()

        with patch(
            "local_deep_research.utilities.db_utils.has_request_context",
            return_value=True,
        ):
            with patch(
                "local_deep_research.utilities.db_utils.flask_session",
                {"username": "webuser"},
            ):
                with patch(
                    "local_deep_research.utilities.db_utils.get_db_session",
                    return_value=mock_session_obj,
                ):
                    with patch(
                        "local_deep_research.settings.SettingsManager"
                    ) as MockSM:
                        mock_sm = Mock()
                        MockSM.return_value = mock_sm

                        result = get_settings_manager()
                        assert result is not None


class TestGetSettingFromDbMainThreadSuccess:
    """Cover get_setting_from_db_main_thread with successful DB read."""

    def test_returns_setting_value_from_db(self):
        """Should return setting value when session and manager work."""
        from local_deep_research.utilities.db_utils import (
            get_setting_from_db_main_thread,
        )

        mock_session = Mock()
        mock_ctx = MagicMock()
        mock_ctx.__enter__ = Mock(return_value=mock_session)
        mock_ctx.__exit__ = Mock(return_value=False)

        mock_manager = Mock()
        mock_manager.get_setting.return_value = "expected_value"

        with patch(
            "local_deep_research.utilities.db_utils.has_app_context",
            return_value=True,
        ):
            with patch(
                "local_deep_research.database.session_context.get_user_db_session",
                return_value=mock_ctx,
            ):
                with patch(
                    "local_deep_research.utilities.db_utils.get_settings_manager",
                    return_value=mock_manager,
                ):
                    result = get_setting_from_db_main_thread(
                        "test.key", default_value="fallback"
                    )

                    assert result == "expected_value"
                    mock_manager.get_setting.assert_called_once_with(
                        "test.key", default="fallback"
                    )

    def test_inner_exception_falls_back_to_default(self):
        """When inner session context raises, falls to outer try and returns default."""
        from local_deep_research.utilities.db_utils import (
            get_setting_from_db_main_thread,
        )

        mock_ctx = MagicMock()
        mock_ctx.__enter__ = Mock(side_effect=Exception("session error"))
        mock_ctx.__exit__ = Mock(return_value=False)

        with patch(
            "local_deep_research.utilities.db_utils.has_app_context",
            return_value=True,
        ):
            with patch(
                "local_deep_research.database.session_context.get_user_db_session",
                return_value=mock_ctx,
            ):
                result = get_setting_from_db_main_thread(
                    "key", default_value="default"
                )
                assert result == "default"

    def test_background_thread_with_has_app_context_true(self):
        """Background thread with app context should NOT raise."""
        from local_deep_research.utilities.db_utils import (
            get_setting_from_db_main_thread,
        )

        results = [None]

        def bg_task():
            with patch(
                "local_deep_research.utilities.db_utils.has_app_context",
                return_value=True,
            ):
                mock_ctx = MagicMock()
                mock_ctx.__enter__ = Mock(return_value=None)
                mock_ctx.__exit__ = Mock(return_value=False)
                with patch(
                    "local_deep_research.database.session_context.get_user_db_session",
                    return_value=mock_ctx,
                ):
                    results[0] = get_setting_from_db_main_thread(
                        "key", default_value="bg_default"
                    )

        thread = threading.Thread(target=bg_task, name="BGWithContext")
        thread.start()
        thread.join()

        # With app context, should not raise, but session is None so returns default
        assert results[0] == "bg_default"
