"""Tests for get_user_password fallback chain in password_utils."""

from unittest.mock import MagicMock, patch

import flask

from local_deep_research.web.auth.password_utils import (
    get_user_password,
    resolve_user_password,
)

MODULE = "local_deep_research.web.auth.password_utils"
DB_MANAGER = "local_deep_research.database.encrypted_db.db_manager"


class TestGetUserPassword:
    """Tests for the 3-source fallback chain in get_user_password."""

    def test_returns_password_from_session_store(self):
        """Path 1: session_password_store has a password for the user."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_store = MagicMock()
            mock_store.get_session_password.return_value = "stored_pass"
            with (
                patch(f"{MODULE}.session", {"session_id": "sid123"}),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_store,
                ),
            ):
                result = get_user_password("alice")
            assert result == "stored_pass"
            mock_store.get_session_password.assert_called_once_with(
                "alice", "sid123"
            )

    def test_returns_password_from_flask_g(self):
        """Path 2: no session password, but g.user_password is set."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_store = MagicMock()
            mock_store.get_session_password.return_value = None
            with (
                patch(f"{MODULE}.session", {"session_id": "sid1"}),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_store,
                ),
                patch(f"{MODULE}.g") as mock_g,
            ):
                mock_g.user_password = "g_password"
                # Make getattr work on the mock
                type(mock_g).user_password = "g_password"
                result = get_user_password("bob")
            assert result == "g_password"

    def test_returns_password_from_temp_auth_token(self):
        """Path 3: temp_auth_token matches username in temp_auth_store."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_session_store = MagicMock()
            mock_session_store.get_session_password.return_value = None
            mock_temp_store = MagicMock()
            mock_temp_store.peek_auth.return_value = ("carol", "temp_pass")

            mock_session = MagicMock()
            mock_session.get.side_effect = lambda k, *a: {
                "session_id": "sid2",
                "temp_auth_token": "tok42",
            }.get(k, *a)

            with (
                patch(f"{MODULE}.session", mock_session),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_session_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_session_store,
                ),
                patch(f"{MODULE}.g") as mock_g,
                patch(
                    "local_deep_research.database.temp_auth.temp_auth_store",
                    mock_temp_store,
                ),
            ):
                mock_g.user_password = None
                type(mock_g).user_password = None
                result = get_user_password("carol")
            assert result == "temp_pass"
            mock_temp_store.peek_auth.assert_called_once_with("tok42")

    def test_returns_none_when_nothing_found(self):
        """Path 4: none of the sources have a password."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_session_store = MagicMock()
            mock_session_store.get_session_password.return_value = None
            mock_temp_store = MagicMock()
            mock_temp_store.peek_auth.return_value = None

            mock_session = MagicMock()
            mock_session.get.side_effect = lambda k, *a: {
                "session_id": "sid3",
                "temp_auth_token": "tok99",
            }.get(k, *a)

            with (
                patch(f"{MODULE}.session", mock_session),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_session_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_session_store,
                ),
                patch(f"{MODULE}.g") as mock_g,
                patch(
                    "local_deep_research.database.temp_auth.temp_auth_store",
                    mock_temp_store,
                ),
            ):
                mock_g.user_password = None
                type(mock_g).user_password = None
                result = get_user_password("dave")
            assert result is None

    def test_returns_none_when_temp_auth_username_mismatch(self):
        """Path 5: temp_auth_token exists but username doesn't match."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_session_store = MagicMock()
            mock_session_store.get_session_password.return_value = None
            mock_temp_store = MagicMock()
            # Token belongs to "other_user", not "eve"
            mock_temp_store.peek_auth.return_value = (
                "other_user",
                "some_pass",
            )

            mock_session = MagicMock()
            mock_session.get.side_effect = lambda k, *a: {
                "session_id": "sid4",
                "temp_auth_token": "tok77",
            }.get(k, *a)

            with (
                patch(f"{MODULE}.session", mock_session),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_session_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_session_store,
                ),
                patch(f"{MODULE}.g") as mock_g,
                patch(
                    "local_deep_research.database.temp_auth.temp_auth_store",
                    mock_temp_store,
                ),
            ):
                mock_g.user_password = None
                type(mock_g).user_password = None
                result = get_user_password("eve")
            assert result is None

    def test_no_session_id_skips_session_store(self):
        """Path 6: no session_id so session password store is skipped."""
        app = flask.Flask(__name__)
        with app.test_request_context():
            mock_session_store = MagicMock()
            # Should never be called
            mock_session_store.get_session_password.return_value = (
                "SHOULD_NOT_USE"
            )

            with (
                patch(f"{MODULE}.session", {"session_id": None}),
                patch(
                    f"{MODULE}.session_password_store",
                    mock_session_store,
                    create=True,
                ),
                patch(
                    "local_deep_research.database.session_passwords.session_password_store",
                    mock_session_store,
                ),
                patch(f"{MODULE}.g") as mock_g,
            ):
                mock_g.user_password = "from_g"
                type(mock_g).user_password = "from_g"
                result = get_user_password("frank")
            assert result == "from_g"
            mock_session_store.get_session_password.assert_not_called()


class TestResolveUserPassword:
    """Tests for resolve_user_password, the shared research-entry guard.

    Returns (password, session_expired); session_expired is True only when
    the DB is encrypted AND no password is available (#4457).
    """

    def test_password_present_is_not_expired(self):
        """A real password -> (password, False), regardless of encryption."""
        with (
            patch(f"{MODULE}.get_user_password", return_value="pw"),
            patch(DB_MANAGER) as mock_db,
        ):
            mock_db.has_encryption = True
            password, session_expired = resolve_user_password("alice")
        assert password == "pw"
        assert session_expired is False

    def test_no_password_encrypted_is_expired(self):
        """Encrypted DB + no password -> (None, True): caller must reject."""
        with (
            patch(f"{MODULE}.get_user_password", return_value=None),
            patch(DB_MANAGER) as mock_db,
        ):
            mock_db.has_encryption = True
            password, session_expired = resolve_user_password("alice")
        assert password is None
        assert session_expired is True

    def test_no_password_unencrypted_is_not_expired(self):
        """Unencrypted DB + no password -> (None, False): caller proceeds."""
        with (
            patch(f"{MODULE}.get_user_password", return_value=None),
            patch(DB_MANAGER) as mock_db,
        ):
            mock_db.has_encryption = False
            password, session_expired = resolve_user_password("alice")
        assert password is None
        assert session_expired is False
