"""Tests for /resume status lines going to stderr in quiet mode (#11793).

The fix in cli._init_agent routes three messages to stderr when
``tool_progress_mode == "off"`` (set by ``hermes chat --quiet``):

  * "Session not found: ..."
  * "↻ Resumed session ... (N user messages, M total messages)"
  * "Session ... found but has no messages. Starting fresh."

Interactive mode (tool_progress_mode == "full") still uses ChatConsole.
"""

from datetime import datetime
from unittest.mock import MagicMock

import pytest


from cli import HermesCLI
from agent.i18n import t


def _make_cli(quiet=False, session_id="20260524_111111_xyz", db=None):
    """Build a minimal HermesCLI bound to only what _init_agent needs for
    the resume code path: _resumed, _session_db, conversation_history,
    session_id, and tool_progress_mode."""
    cli = HermesCLI.__new__(HermesCLI)
    cli.session_id = session_id
    cli._resumed = True
    cli.conversation_history = []
    cli._session_db = db
    cli.tool_progress_mode = "off" if quiet else "full"
    cli.session_start = datetime.now()
    cli.agent = None
    # We need _init_agent to reach the resume block (line ~4757) but not
    # proceed into actual AIAgent construction. _ensure_runtime_credentials
    # must return True (False returns early at line 4743). _install_tool_callbacks
    # is stubbed; the resume block will either return
    # False (session-not-found) or reach the eventual AIAgent() call which
    # we'll let raise — we only check stdout/stderr printed BEFORE that.
    cli._install_tool_callbacks = lambda: None
    cli._ensure_runtime_credentials = lambda: True
    return cli


class TestResumeQuietStderr:
    def test_session_not_found_goes_to_stderr_in_quiet_mode(self, capsys):
        db = MagicMock()
        db.get_session.return_value = None
        cli = _make_cli(quiet=True, db=db)

        result = cli._init_agent()

        captured = capsys.readouterr()
        assert result is False
        # stdout must stay clean
        assert t("cli.resume.session_not_found", session_id=cli.session_id) not in captured.out
        # the resume status goes to stderr
        assert t("cli.resume.session_not_found", session_id=cli.session_id) in captured.err
        assert "hermes sessions list" in captured.err

    def test_session_not_found_goes_to_stdout_in_full_mode(self, capsys):
        db = MagicMock()
        db.get_session.return_value = None
        cli = _make_cli(quiet=False, db=db)

        result = cli._init_agent()

        captured = capsys.readouterr()
        assert result is False
        # Interactive mode keeps the existing _cprint path → stdout.
        assert t("cli.resume.session_not_found", session_id=cli.session_id) in captured.out

    def test_resumed_banner_goes_to_stderr_in_quiet_mode(self, capsys):
        db = MagicMock()
        db.get_session.return_value = {"id": "20260524_111111_xyz", "title": "demo"}
        db.resolve_resume_session_id.return_value = "20260524_111111_xyz"
        db.get_messages_as_conversation.return_value = [
            {"role": "user", "content": "hi"},
            {"role": "assistant", "content": "hey"},
        ]
        db._conn = MagicMock()  # for the reopen execute() call

        cli = _make_cli(quiet=True, db=db)
        # Stop _init_agent right after the resume banner: prevent it from
        # constructing a real AIAgent (the next code path).
        try:
            cli._init_agent()
        except Exception:
            # The post-resume agent-init machinery may fail in this
            # stubbed context (no API key, no real config) — we only
            # care about the printed banner that comes earlier.
            pass

        captured = capsys.readouterr()
        # Banner on stderr — stdout stays clean for automation.
        assert t("cli.resume.resumed_session") not in captured.out
        assert t("cli.resume.resumed_session") in captured.err
        assert "20260524_111111_xyz" in captured.err
        assert "demo" in captured.err

    def test_no_messages_goes_to_stderr_in_quiet_mode(self, capsys):
        db = MagicMock()
        db.get_session.return_value = {"id": "20260524_111111_xyz"}
        db.resolve_resume_session_id.return_value = "20260524_111111_xyz"
        db.get_messages_as_conversation.return_value = []
        db._conn = MagicMock()

        cli = _make_cli(quiet=True, db=db)
        try:
            cli._init_agent()
        except Exception:
            pass

        captured = capsys.readouterr()
        assert "has no messages" not in captured.out
        assert "has no messages" in captured.err
        assert "Starting fresh" in captured.err

    def test_resume_policy_refusal_is_a_usage_error_not_an_agent_init_failure(self, capsys):
        """`--resume X --max-turns 1 -q ...` printed the agent-init failure copy ("couldn't start the model
        connection ... hermes doctor") and exited 1; it is interactive startup's `Error:` line on stderr
        with exit 2. Any other failed open keeps the agent-init copy."""
        from hermes_cli.gateway_chat import _RESUME_POLICY_MISMATCH
        from hermes_cli.gateway_client import GatewayClientError
        cli = _make_cli(quiet=True, db=MagicMock())
        refusal = f"{_RESUME_POLICY_MISMATCH} --max-turns differs from this session's launch."

        def refuse():
            raise GatewayClientError(refusal)
        cli._ensure_gateway_session = refuse
        with pytest.raises(SystemExit) as exited:
            cli._init_agent()
        captured = capsys.readouterr()
        assert exited.value.code == 2
        assert captured.err.strip() == f"Error: {refusal}"
        assert "couldn't start" not in captured.out + captured.err

        def unreachable():
            raise GatewayClientError("gateway unavailable")
        cli._ensure_gateway_session = unreachable
        assert cli._init_agent() is False
