"""Tests for mld_sdk._prompt — interactive terminal prompts."""

from __future__ import annotations

from unittest.mock import patch

import pytest

from mld_sdk._prompt import prompt_confirm, prompt_multiselect, prompt_select, prompt_text


class TestPromptText:
    """Tests for prompt_text (free-form input)."""

    def test_returns_default_when_not_interactive(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_text("Name", "fallback") == "fallback"

    def test_returns_user_input(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("builtins.input", return_value="hello"),
        ):
            assert prompt_text("Name", "default") == "hello"

    def test_returns_default_on_empty_input(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("builtins.input", return_value=""),
        ):
            assert prompt_text("Name", "default") == "default"

    def test_strips_whitespace(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("builtins.input", return_value="  padded  "),
        ):
            assert prompt_text("Name") == "padded"

    def test_exits_on_keyboard_interrupt(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("builtins.input", side_effect=KeyboardInterrupt),
            pytest.raises(SystemExit),
        ):
            prompt_text("Name")

    def test_exits_on_eof(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("builtins.input", side_effect=EOFError),
            pytest.raises(SystemExit),
        ):
            prompt_text("Name")


class TestPromptSelect:
    """Tests for prompt_select (arrow-key selection)."""

    def test_returns_default_when_not_interactive(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_select("Type", ["A", "B", "C"], default=1) == 1

    def test_returns_default_index_zero(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_select("Type", ["A", "B"]) == 0

    def test_enter_selects_current(self):
        """Pressing Enter immediately returns the default."""
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\r"),
            patch("sys.stdout"),
        ):
            assert prompt_select("Type", ["A", "B"], default=0) == 0

    def test_down_then_enter(self):
        """Down arrow then Enter selects second item."""
        keys = iter(["\x1b[B", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            assert prompt_select("Type", ["A", "B"], default=0) == 1

    def test_wraps_around_bottom(self):
        """Down from last item wraps to first."""
        keys = iter(["\x1b[B", "\x1b[B", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            assert prompt_select("Type", ["A", "B"], default=0) == 0

    def test_up_wraps_to_bottom(self):
        """Up from first item wraps to last."""
        keys = iter(["\x1b[A", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            assert prompt_select("Type", ["A", "B"], default=0) == 1

    def test_ctrl_c_exits(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\x03"),
            patch("sys.stdout"),
            pytest.raises(SystemExit),
        ):
            prompt_select("Type", ["A", "B"])


class TestPromptMultiselect:
    """Tests for prompt_multiselect (checkbox selection)."""

    def test_returns_defaults_when_not_interactive(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_multiselect("Tools", ["A", "B", "C"], defaults=[0, 2]) == [0, 2]

    def test_returns_empty_when_not_interactive_no_defaults(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_multiselect("Tools", ["A", "B"]) == []

    def test_enter_confirms_defaults(self):
        """Enter immediately returns the pre-selected defaults."""
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\r"),
            patch("sys.stdout"),
        ):
            assert prompt_multiselect("Tools", ["A", "B", "C"], defaults=[0]) == [0]

    def test_space_toggles_selection(self):
        """Space toggles item 0 off, down+space toggles item 1 on, then enter."""
        keys = iter([" ", "\x1b[B", " ", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            result = prompt_multiselect("Tools", ["A", "B", "C"], defaults=[0])
            assert result == [1]

    def test_multiple_selections(self):
        """Select items 0 and 2."""
        # Start at 0 (default), space to select, down, down, space to select, enter
        keys = iter([" ", "\x1b[B", "\x1b[B", " ", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            result = prompt_multiselect("Tools", ["A", "B", "C"])
            assert result == [0, 2]

    def test_empty_selection(self):
        """Enter with nothing selected returns empty list."""
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\r"),
            patch("sys.stdout"),
        ):
            assert prompt_multiselect("Tools", ["A", "B"]) == []

    def test_ctrl_c_exits(self):
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\x03"),
            patch("sys.stdout"),
            pytest.raises(SystemExit),
        ):
            prompt_multiselect("Tools", ["A", "B"])


class TestPromptConfirm:
    """Tests for prompt_confirm (yes/no selection)."""

    def test_default_true_when_not_interactive(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_confirm("Continue?", default=True) is True

    def test_default_false_when_not_interactive(self):
        with patch("mld_sdk._prompt._is_interactive", return_value=False):
            assert prompt_confirm("Continue?", default=False) is False

    def test_select_yes(self):
        """Enter on default selects Yes."""
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", return_value="\r"),
            patch("sys.stdout"),
        ):
            assert prompt_confirm("Continue?") is True

    def test_select_no(self):
        """Down then Enter selects No."""
        keys = iter(["\x1b[B", "\r"])
        with (
            patch("mld_sdk._prompt._is_interactive", return_value=True),
            patch("mld_sdk._prompt._read_key", side_effect=keys),
            patch("sys.stdout"),
        ):
            assert prompt_confirm("Continue?") is False
