# tests/test_edge_cases.py
from io import StringIO
import pytest
from receptus import Receptus, UserQuit


def _mk():
    buf = StringIO()
    r = Receptus(force_no_color=True, output=buf)
    return r, buf


def test_all_options_disabled(monkeypatch):
    """When every option is disabled, no selection is possible."""
    r, buf = _mk()
    seq = iter(["a", "b", "c"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(
        options={"a": "Alpha", "b": "Beta"},
        disabled_keys={"a", "b"},
        attempts=3,
    )
    assert res is None
    assert "disabled" in buf.getvalue().lower() or "not a valid" in buf.getvalue().lower()


def test_callable_options_raises(monkeypatch):
    """Callable options that raise should not crash the prompt loop."""
    r, buf = _mk()

    def bad_options():
        raise RuntimeError("options exploded")

    seq = iter(["a"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(options=bad_options, attempts=1, allow_free_text=True)
    assert "Error loading options" in buf.getvalue()


def test_help_callback_raises(monkeypatch):
    """help_callback that raises should not crash the prompt loop."""
    r, buf = _mk()

    def bad_help():
        raise RuntimeError("help exploded")

    seq = iter(["help", "a"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(options={"a": "Alpha"}, help_callback=bad_help)
    assert res == "a"


def test_on_event_callback_raises(monkeypatch):
    """on_event callback that raises should not crash the prompt loop."""

    def bad_event(event_type, context):
        raise RuntimeError("event exploded")

    buf = StringIO()
    r = Receptus(force_no_color=True, output=buf, on_event=bad_event)
    monkeypatch.setattr("builtins.input", lambda _: "a")
    res = r.get_input(options={"a": "Alpha"})
    assert res == "a"


def test_invalid_return_format():
    """Invalid return_format should raise ValueError."""
    r, _ = _mk()
    with pytest.raises(ValueError, match="return_format must be one of"):
        r.get_input(return_format="bad")


def test_multi_select_return_format_value(monkeypatch):
    """Multi-select with return_format='value' returns option values."""
    r, _ = _mk()
    monkeypatch.setattr("builtins.input", lambda _: "a,b")
    res = r.get_input(
        options={"a": "Alpha", "b": "Beta"},
        allow_multi=True,
        return_format="value",
    )
    assert res == ["Alpha", "Beta"]


def test_max_input_len_zero(monkeypatch):
    """max_input_len=0 should effectively disable the length check (falsy)."""
    r, buf = _mk()
    monkeypatch.setattr("builtins.input", lambda _: "a")
    res = r.get_input(options={"a": "Alpha"}, max_input_len=0)
    assert res == "a"


def test_max_input_len_none(monkeypatch):
    """max_input_len=None should disable the length check."""
    r, _ = _mk()
    long_input = "a"
    monkeypatch.setattr("builtins.input", lambda _: long_input)
    res = r.get_input(options={"a": "Alpha"}, max_input_len=None)
    assert res == "a"


def test_fuzzy_cutoff_zero(monkeypatch):
    """fuzzy_cutoff=0.0 should match almost anything."""
    r, buf = _mk()
    seq = iter(["zzz", "apple"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(
        options={"apple": "Apple", "banana": "Banana"},
        fuzzy_match=True,
        fuzzy_cutoff=0.0,
    )
    # First input "zzz" should trigger fuzzy suggestion (cutoff=0 matches everything)
    assert "Did you mean" in buf.getvalue()
    assert res == "apple"


def test_fuzzy_cutoff_one(monkeypatch):
    """fuzzy_cutoff=1.0 should only match exact matches (no fuzzy suggestions)."""
    r, buf = _mk()
    monkeypatch.setattr("builtins.input", lambda _: "appl")
    res = r.get_input(
        options={"apple": "Apple"},
        fuzzy_match=True,
        fuzzy_cutoff=1.0,
        attempts=1,
    )
    # "appl" is close but not exact — with cutoff=1.0, no fuzzy match
    assert "Did you mean" not in buf.getvalue()
    assert res is None


def test_default_and_current_value_both_set(monkeypatch):
    """current_value takes precedence over default when both are set."""
    r, _ = _mk()
    monkeypatch.setattr("builtins.input", lambda _: "")
    res = r.get_input(
        options={"a": "Alpha", "b": "Beta"},
        default="a",
        current_value="b",
    )
    assert res == "b"


def test_quit_word_none(monkeypatch):
    """quit_word=None should disable the quit command entirely."""
    r, _ = _mk()
    seq = iter(["quit", "a"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(options={"a": "Alpha"}, quit_word=None)
    # "quit" is not recognized as quit command, falls through to invalid option
    # then "a" succeeds
    assert res == "a"


def test_help_word_none(monkeypatch):
    """help_word=None should disable the help command entirely."""
    r, _ = _mk()
    seq = iter(["help", "a"])
    monkeypatch.setattr("builtins.input", lambda _: next(seq))
    res = r.get_input(options={"a": "Alpha"}, help_word=None)
    assert res == "a"


def test_init_invalid_line_sep():
    """Non-string line_sep should raise TypeError."""
    with pytest.raises(TypeError, match="line_sep must be a string"):
        Receptus(line_sep=123)


def test_init_invalid_line_end():
    """Non-string line_end should raise TypeError."""
    with pytest.raises(TypeError, match="line_end must be a string"):
        Receptus(line_end=None)
