"""The fork-upstream prompt must never block a non-interactive update (#60240).

`_sync_with_upstream_if_needed` asks "Add official repo as 'upstream' remote?"
on fork checkouts with no upstream remote. In unattended contexts (CI, cron,
the desktop updater hand-off) stdin is open but nobody answers, so a bare
``input()`` blocks forever. These tests pin the gate: under ``assume_yes`` or
a non-TTY stdio pair the prompt is skipped as a decline WITHOUT persisting the
skip marker or touching git remotes, and both update call sites forward the
interaction state.
"""

from types import SimpleNamespace
from unittest.mock import patch

import pytest

from hermes_cli import update_cmd


@pytest.fixture
def fork_without_upstream(tmp_path):
    with patch.object(
        update_cmd, "_has_upstream_remote", return_value=False
    ), patch.object(
        update_cmd, "_should_skip_upstream_prompt", return_value=False
    ), patch.object(
        update_cmd, "_add_upstream_remote", return_value=True
    ) as add_remote, patch.object(
        update_cmd, "_mark_skip_upstream_prompt"
    ) as mark_skip, patch("builtins.input") as stdin_input:
        yield SimpleNamespace(
            cwd=tmp_path,
            add_remote=add_remote,
            mark_skip=mark_skip,
            stdin_input=stdin_input,
        )


def _tty(stdin: bool, stdout: bool):
    return (
        patch.object(update_cmd.sys.stdin, "isatty", return_value=stdin),
        patch.object(update_cmd.sys.stdout, "isatty", return_value=stdout),
    )


class TestUpstreamPromptNonInteractive:
    def test_assume_yes_skips_prompt_without_touching_remotes(
        self, fork_without_upstream, capsys
    ):
        p_in, p_out = _tty(True, True)
        with p_in, p_out:
            checked = update_cmd._sync_with_upstream_if_needed(
                ["git"], fork_without_upstream.cwd, assume_yes=True
            )

        assert checked is False
        fork_without_upstream.stdin_input.assert_not_called()
        fork_without_upstream.add_remote.assert_not_called()
        fork_without_upstream.mark_skip.assert_not_called()

    @pytest.mark.parametrize(
        "stdin_tty,stdout_tty", [(False, False), (False, True), (True, False)]
    )
    def test_non_tty_skips_prompt_without_persisting_decline(
        self, fork_without_upstream, capsys, stdin_tty, stdout_tty
    ):
        p_in, p_out = _tty(stdin_tty, stdout_tty)
        with p_in, p_out:
            checked = update_cmd._sync_with_upstream_if_needed(
                ["git"], fork_without_upstream.cwd
            )

        assert checked is False
        fork_without_upstream.stdin_input.assert_not_called()
        fork_without_upstream.add_remote.assert_not_called()
        fork_without_upstream.mark_skip.assert_not_called()

    def test_gateway_prompt_routes_through_input_fn(self, fork_without_upstream):
        prompts = []

        def gw_input(prompt, default=""):
            prompts.append((prompt, default))
            return "n"

        p_in, p_out = _tty(False, False)
        with p_in, p_out:
            update_cmd._sync_with_upstream_if_needed(
                ["git"], fork_without_upstream.cwd, input_fn=gw_input
            )

        assert len(prompts) == 1 and prompts[0][1] == "n"
        fork_without_upstream.stdin_input.assert_not_called()
        fork_without_upstream.add_remote.assert_not_called()
        fork_without_upstream.mark_skip.assert_called_once_with()

    def test_interactive_decline_still_persists_marker(self, fork_without_upstream):
        fork_without_upstream.stdin_input.return_value = "n"
        p_in, p_out = _tty(True, True)
        with p_in, p_out:
            update_cmd._sync_with_upstream_if_needed(
                ["git"], fork_without_upstream.cwd
            )

        fork_without_upstream.stdin_input.assert_called_once()
        fork_without_upstream.add_remote.assert_not_called()
        fork_without_upstream.mark_skip.assert_called_once_with()

    def test_interactive_accept_adds_upstream(self, fork_without_upstream):
        fork_without_upstream.stdin_input.return_value = "y"
        with patch.object(
            update_cmd, "_count_commits_between", return_value=-1
        ), patch.object(update_cmd.subprocess, "run"):
            p_in, p_out = _tty(True, True)
            with p_in, p_out:
                update_cmd._sync_with_upstream_if_needed(
                    ["git"], fork_without_upstream.cwd
                )

        fork_without_upstream.add_remote.assert_called_once_with(
            ["git"], fork_without_upstream.cwd
        )
        fork_without_upstream.mark_skip.assert_not_called()
