from unittest.mock import Mock

import pytest

from fenrirscreenreader.core.speechHistoryManager import SpeechHistoryManager


def build_speech_history_manager(history_size=3):
    spoken_messages = []
    output_manager = Mock()

    def _capture_message(message, **_kwargs):
        spoken_messages.append(message)

    output_manager.present_text.side_effect = _capture_message
    settings_manager = Mock()
    settings_manager.get_setting_as_int.return_value = history_size
    memory_manager = Mock(add_value_to_first_index=Mock())
    input_manager = Mock(reset_input_state=Mock())
    input_driver = Mock(refresh_grabs=Mock())
    env = {
        "runtime": {
            "OutputManager": output_manager,
            "SettingsManager": settings_manager,
            "MemoryManager": memory_manager,
            "InputManager": input_manager,
            "InputDriver": input_driver,
        },
        "bindings": {"original": "COMMAND"},
        "rawBindings": {"original": [1, ["KEY_FENRIR"]]},
    }
    manager = SpeechHistoryManager()
    manager.initialize(env)
    return manager, env, spoken_messages, memory_manager


@pytest.mark.unit
def test_speech_history_keeps_configured_number_of_items():
    manager, _env, _spoken_messages, _memory_manager = (
        build_speech_history_manager(history_size=2)
    )

    assert manager.add_text("one")
    assert manager.add_text("two")
    assert manager.add_text("three")

    assert manager.history == ["three", "two"]


@pytest.mark.unit
def test_speech_history_suppresses_exact_duplicates_until_item_drops():
    manager, _env, _spoken_messages, _memory_manager = (
        build_speech_history_manager(history_size=2)
    )

    assert manager.add_text("hello world")
    assert not manager.add_text("hello world")
    assert manager.add_text("other")
    assert manager.add_text("third")
    assert manager.add_text("hello world")

    assert manager.history == ["hello world", "third"]


@pytest.mark.unit
def test_speech_history_dedupe_keeps_case_and_suppresses_whitespace_variants():
    manager, _env, _spoken_messages, _memory_manager = (
        build_speech_history_manager()
    )

    assert manager.add_text("hello")
    assert manager.add_text("Hello")
    assert not manager.add_text("hello ")
    assert not manager.add_text("hello        ")

    assert manager.history == ["Hello", "hello"]


@pytest.mark.unit
def test_open_empty_history_announces_empty_without_modal_bindings():
    manager, env, spoken_messages, _memory_manager = (
        build_speech_history_manager()
    )

    assert not manager.open_history()

    assert not manager.is_active()
    assert spoken_messages == ["speech history empty"]
    assert env["bindings"] == {"original": "COMMAND"}


@pytest.mark.unit
def test_open_history_installs_modal_bindings_and_replay_is_not_recorded():
    manager, env, spoken_messages, _memory_manager = (
        build_speech_history_manager()
    )
    env["rawBindings"]["ctrl_shut_up"] = [1, ["KEY_CTRL"]]
    manager.add_text("first")
    manager.add_text("second")

    assert manager.open_history()
    manager.add_text("replayed")

    assert manager.is_active()
    assert spoken_messages == ["Speech history"]
    assert manager.curr_index == -1
    assert manager.history == ["second", "first"]
    assert "original" not in env["bindings"]
    assert env["rawBindings"]["original"] == [1, ["KEY_FENRIR"]]
    assert env["rawBindings"]["ctrl_shut_up"] == [1, ["KEY_CTRL"]]
    assert env["bindings"][str([1, ["KEY_UP"]])] == "SPEECH_HISTORY_PREV"
    assert env["bindings"][str([1, ["KEY_ENTER"]])] == "SPEECH_HISTORY_COPY"
    assert env["bindings"][str([1, ["KEY_ESC"]])] == "SPEECH_HISTORY_CLOSE"
    assert env["rawBindings"][str([1, ["KEY_UP"]])] == [1, ["KEY_UP"]]
    input_driver = env["runtime"]["InputDriver"]
    input_driver.refresh_grabs.assert_called_once_with(force=True)


@pytest.mark.unit
def test_navigation_moves_between_newer_and_older_items():
    manager, _env, spoken_messages, _memory_manager = (
        build_speech_history_manager()
    )
    manager.add_text("oldest")
    manager.add_text("middle")
    manager.add_text("newest")
    manager.open_history()

    manager.prev_entry()
    manager.prev_entry()
    manager.prev_entry()
    manager.next_entry()

    assert spoken_messages[-4:] == ["newest", "middle", "oldest", "middle"]


@pytest.mark.unit
def test_copy_current_adds_clipboard_and_restores_bindings():
    manager, env, spoken_messages, memory_manager = (
        build_speech_history_manager()
    )
    manager.add_text("first")
    manager.add_text("second")
    manager.open_history()
    manager.prev_entry()
    manager.prev_entry()

    manager.copy_current_to_clipboard()

    memory_manager.add_value_to_first_index.assert_called_once_with(
        "clipboardHistory", "first"
    )
    assert spoken_messages[-1] == "copied to clipboard"
    assert not manager.is_active()
    assert env["bindings"] == {"original": "COMMAND"}
    assert env["rawBindings"] == {"original": [1, ["KEY_FENRIR"]]}
    env["runtime"]["InputManager"].reset_input_state.assert_called_once_with()
    assert env["runtime"]["InputDriver"].refresh_grabs.call_count == 2


@pytest.mark.unit
def test_close_history_restores_keyboard_state_and_grabs():
    manager, env, _spoken_messages, _memory_manager = (
        build_speech_history_manager()
    )
    manager.add_text("first")
    manager.open_history()

    manager.close_history()

    assert not manager.is_active()
    assert env["bindings"] == {"original": "COMMAND"}
    assert env["rawBindings"] == {"original": [1, ["KEY_FENRIR"]]}
    env["runtime"]["InputManager"].reset_input_state.assert_called_once_with()
    assert env["runtime"]["InputDriver"].refresh_grabs.call_count == 2
