"""
Shared utilities for resolving the current user's DB password.

``get_user_password`` centralises the 3-source fallback chain (session
password store → Flask g → temp auth store) so every route that needs the
current user's DB password uses the same logic.  Without this, each route
reimplemented the chain independently, risking subtle divergence (e.g. one
route forgetting to check temp_auth, or using a different method alias).

``resolve_user_password`` builds on it with the encryption-aware guard the
research entry points share: it returns ``(password, session_expired)`` so
a caller can reject a run when an encrypted DB has no available password.
"""

from typing import Optional, Tuple

from flask import g, session
from loguru import logger


def get_user_password(username: str) -> Optional[str]:
    """Retrieve user password from available session sources.

    Checks, in order:
    1. SessionPasswordStore (persistent per-session passwords)
    2. Flask ``g.user_password`` (set by middleware when temp_auth was used)
    3. TempAuthStore (one-time tokens stored during login redirect)

    Returns ``None`` when no password can be found — callers must decide
    whether that is acceptable (e.g. non-encrypted databases) or an error
    (encrypted databases → 401).
    """
    from ...database.session_passwords import session_password_store

    session_id = session.get("session_id")
    if session_id:
        password = session_password_store.get_session_password(
            username, session_id
        )
        if password:
            return password

    password = getattr(g, "user_password", None)
    if password:
        return password

    from ...database.temp_auth import temp_auth_store

    auth_token = session.get("temp_auth_token")
    if auth_token:
        auth_data = temp_auth_store.peek_auth(auth_token)
        if auth_data and auth_data[0] == username:
            return auth_data[1]

    return None


def resolve_user_password(username: str) -> Tuple[Optional[str], bool]:
    """Resolve the user's DB password for starting a research run.

    Returns ``(password, session_expired)``:

    - ``session_expired`` is ``True`` only when the database is encrypted
      and no password is available. The caller MUST reject the request
      (e.g. a 401 telling the user to log back in) because the research's
      background DB and metric writes would otherwise be silently dropped
      (issue #4457) — research would appear to run while every metric write
      fails. Trigger: session-password-store TTL expiry or a server/
      container restart while the session cookie is still valid.
    - For unencrypted databases ``session_expired`` is always ``False``;
      the returned ``password`` may legitimately be ``None``.

    Centralises the guard that the direct (``/start_research``), follow-up,
    and chat research entry points all need, so the encryption-aware
    decision and its logging live in one place instead of being copied
    (and risking divergence) across routes. Each route still formats its
    own error response, because their frontends expect different shapes.
    """
    from ...database.encrypted_db import db_manager

    password = get_user_password(username)  # gitleaks:allow
    if not password and db_manager.has_encryption:
        logger.error(
            f"No password available for user {username} with encrypted "
            "database - cannot start research (session password expired or "
            "lost after server restart)"
        )
        return None, True
    if not password:
        logger.warning(
            f"No password available for metrics access for user {username} "
            "(unencrypted database)"
        )
    return password, False
