"""Deep coverage tests for database/session_context.py.

Focuses on uncovered branches:
- get_user_db_session: g.db_session reuse path
- get_user_db_session: g.user_password path
- get_user_db_session: session_password_store path
- get_user_db_session: thread context password path
- get_user_db_session: unencrypted DB placeholder
- get_user_db_session: failed session raises DatabaseSessionError
- ensure_db_session: user not in session (unauthenticated)
- ensure_db_session: db not connected + encrypted -> redirect
- ensure_db_session: db not connected + unencrypted -> reopen
- UNENCRYPTED_DB_PLACEHOLDER constant
- DatabaseAccessMixin.execute_with_db
"""

from unittest.mock import MagicMock, patch

import pytest

MODULE = "local_deep_research.database.session_context"


# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------


class TestConstants:
    def test_unencrypted_placeholder_is_string(self):
        from local_deep_research.database.session_context import (
            UNENCRYPTED_DB_PLACEHOLDER,
        )

        assert isinstance(UNENCRYPTED_DB_PLACEHOLDER, str)
        assert len(UNENCRYPTED_DB_PLACEHOLDER) > 0

    def test_database_session_error_is_exception(self):
        from local_deep_research.database.session_context import (
            DatabaseSessionError,
        )

        assert issubclass(DatabaseSessionError, Exception)


# ---------------------------------------------------------------------------
# get_user_db_session – happy paths
# ---------------------------------------------------------------------------


class TestGetUserDbSessionHappyPaths:
    def test_uses_g_db_session_when_available(self):
        """When g.db_session exists, it is yielded without opening a new session."""
        from local_deep_research.database.session_context import (
            get_user_db_session,
        )

        mock_sess = MagicMock()
        mock_g = MagicMock()
        mock_g.db_session = mock_sess

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=True),
            patch(f"{MODULE}.g", mock_g),
        ):
            with get_user_db_session(username="alice") as sess:
                assert sess is mock_sess

    def test_unencrypted_db_uses_placeholder_password(  # DevSkim: ignore DS101155
        self,
    ):
        """When no password and db is unencrypted, placeholder password is used."""  # DevSkim: ignore DS101155
        from local_deep_research.database.session_context import (
            get_user_db_session,
        )

        mock_sess = MagicMock()
        mock_g = MagicMock(spec=[])  # No db_session attribute

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=False),
            patch(f"{MODULE}.get_search_context", return_value=None),
            patch(f"{MODULE}.db_manager") as mock_db,
            patch(
                "local_deep_research.database.thread_local_session.thread_session_manager.get_session",
                return_value=mock_sess,
            ),
            patch(f"{MODULE}.g", mock_g),
        ):
            mock_db.has_encryption = False
            with get_user_db_session(username="alice") as sess:
                assert sess is mock_sess

    def test_password_from_thread_context(self):
        """Password is retrieved from thread search context."""
        from local_deep_research.database.session_context import (
            get_user_db_session,
        )

        mock_sess = MagicMock()
        mock_g = MagicMock(spec=[])

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=False),
            patch(
                f"{MODULE}.get_search_context",
                return_value={"user_password": "thread_pw"},
            ),
            patch(f"{MODULE}.db_manager") as mock_db,
            patch(
                "local_deep_research.database.thread_local_session.thread_session_manager.get_session",
                return_value=mock_sess,
            ),
            patch(f"{MODULE}.g", mock_g),
        ):
            mock_db.has_encryption = True
            with get_user_db_session(username="alice") as sess:
                assert sess is mock_sess


# ---------------------------------------------------------------------------
# get_user_db_session – error paths
# ---------------------------------------------------------------------------


class TestGetUserDbSessionErrors:
    def test_no_username_raises(self):
        from local_deep_research.database.session_context import (
            get_user_db_session,
            DatabaseSessionError,
        )

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=False),
        ):
            with pytest.raises(
                DatabaseSessionError, match="No authenticated user"
            ):
                with get_user_db_session():
                    pass

    def test_encrypted_db_no_password_raises(self):
        from local_deep_research.database.session_context import (
            get_user_db_session,
            DatabaseSessionError,
        )

        mock_g = MagicMock(spec=[])

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=False),
            patch(f"{MODULE}.get_search_context", return_value=None),
            patch(f"{MODULE}.db_manager") as mock_db,
            patch(f"{MODULE}.g", mock_g),
        ):
            mock_db.has_encryption = True
            with pytest.raises(DatabaseSessionError, match="requires password"):
                with get_user_db_session(username="alice"):
                    pass

    def test_failed_session_raises(self):
        """When get_metrics_session returns None, DatabaseSessionError is raised."""
        from local_deep_research.database.session_context import (
            get_user_db_session,
            DatabaseSessionError,
        )

        mock_g = MagicMock(spec=[])

        with (
            patch(f"{MODULE}.has_request_context", return_value=False),
            patch(f"{MODULE}.has_app_context", return_value=False),
            patch(f"{MODULE}.get_search_context", return_value=None),
            patch(f"{MODULE}.db_manager") as mock_db,
            patch(
                "local_deep_research.database.thread_local_session.thread_session_manager.get_session",
                return_value=None,
            ),
            patch(f"{MODULE}.g", mock_g),
        ):
            mock_db.has_encryption = False
            with pytest.raises(
                DatabaseSessionError, match="Could not establish session"
            ):
                with get_user_db_session(username="alice"):
                    pass


# ---------------------------------------------------------------------------
# with_user_database decorator
# ---------------------------------------------------------------------------


class TestWithUserDatabase:
    def test_injects_session_as_first_arg(self):
        from local_deep_research.database.session_context import (
            with_user_database,
        )

        mock_sess = MagicMock()

        @with_user_database
        def my_func(db_session, x, y=0):
            return (db_session, x + y)

        with patch(f"{MODULE}.get_user_db_session") as mock_ctx:
            mock_ctx.return_value.__enter__ = MagicMock(return_value=mock_sess)
            mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
            result = my_func(10, y=5)

        assert result == (mock_sess, 15)

    def test_passes_username_and_password_from_kwargs(self):
        from local_deep_research.database.session_context import (
            with_user_database,
        )

        @with_user_database
        def my_func(db_session):
            return db_session

        mock_sess = MagicMock()
        with patch(f"{MODULE}.get_user_db_session") as mock_ctx:
            mock_ctx.return_value.__enter__ = MagicMock(return_value=mock_sess)
            mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
            result = my_func(_username="alice", _password="pw")

        mock_ctx.assert_called_once_with("alice", "pw")
        assert result is mock_sess


# ---------------------------------------------------------------------------
# DatabaseAccessMixin
# ---------------------------------------------------------------------------


class TestDatabaseAccessMixin:
    def test_get_db_session_raises_deprecation_warning(self):
        from local_deep_research.database.session_context import (
            DatabaseAccessMixin,
        )

        class MyService(DatabaseAccessMixin):
            pass

        svc = MyService()
        with pytest.raises(DeprecationWarning):
            svc.get_db_session()

    def test_get_db_session_message_content(self):
        from local_deep_research.database.session_context import (
            DatabaseAccessMixin,
        )

        svc = DatabaseAccessMixin()
        try:
            svc.get_db_session()
        except DeprecationWarning as e:
            assert "deprecated" in str(
                e
            ).lower() or "get_user_db_session" in str(e)
