"""Tests for the daemon-thread excepthook.

Covers:
- _install_thread_excepthook logs uncaught exceptions from daemon threads.
- _perform_post_login_tasks wraps its body so an otherwise-uncaught exception
  from inside the `with` context managers is logged instead of disappearing.
"""

import threading
from unittest.mock import MagicMock, patch


def test_install_thread_excepthook_logs_uncaught_exception():
    """After installing the hook, an uncaught exception on a daemon thread
    must be logged at ERROR level including the exception type and the
    thread name. Without this, silent crashes in the queue processor
    or APScheduler jobs are the mechanism by which the login path
    gradually starves.
    """
    from local_deep_research.web.app import _install_thread_excepthook

    previous = threading.excepthook
    _install_thread_excepthook()
    try:
        with patch("local_deep_research.web.app.logger") as mock_logger:

            def raiser():
                raise RuntimeError("boom from daemon")

            t = threading.Thread(target=raiser, name="test-daemon", daemon=True)
            t.start()
            t.join(timeout=2.0)

            assert not t.is_alive()
            assert mock_logger.error.called, "excepthook did not log"

            logged = mock_logger.error.call_args[0][0]
            assert "test-daemon" in logged
            assert "RuntimeError" in logged
            assert "boom from daemon" in logged
    finally:
        threading.excepthook = previous


def test_perform_post_login_tasks_catches_outer_exceptions():
    """If the outer structure of _perform_post_login_tasks itself raises
    (for example from inside a `with` context manager's __enter__) —
    not inside a per-step try/except — the wrapper must log the
    exception and not let the daemon thread die silently.
    """
    from local_deep_research.web.auth import routes

    with (
        patch.object(
            routes,
            "_perform_post_login_tasks_body",
            side_effect=RuntimeError("outer failure"),
        ),
        patch.object(routes, "logger") as mock_logger,
    ):
        # Must not raise.
        routes._perform_post_login_tasks("alice", "pw")

        assert mock_logger.exception.called
        msg = mock_logger.exception.call_args[0][0]
        assert "alice" in msg
        assert "crashed" in msg.lower() or "post-login" in msg.lower()


def test_perform_post_login_tasks_body_runs_when_no_outer_error():
    """Positive path: when the body succeeds, the wrapper forwards to
    it exactly once with the right args, and does not log any
    crash.
    """
    from local_deep_research.web.auth import routes

    body = MagicMock()
    with (
        patch.object(routes, "_perform_post_login_tasks_body", body),
        patch.object(routes, "logger") as mock_logger,
    ):
        routes._perform_post_login_tasks("bob", "pw")

    body.assert_called_once_with("bob", "pw")
    mock_logger.exception.assert_not_called()
