import threading
import time
from unittest.mock import Mock

import pytest

from fenrirscreenreader.core.eventData import FenrirEventType
from fenrirscreenreader.screenDriver.ptyDriver import PTYConstants
from fenrirscreenreader.screenDriver.ptyDriver import Terminal
from fenrirscreenreader.screenDriver.ptyDriver import driver as PtyDriver


class DummyProcessInput:
    def __init__(self):
        self.data = []

    def write(self, data):
        self.data.append(data)


@pytest.mark.unit
def test_csi_sequences_with_intermediate_characters_do_not_render_final_byte():
    terminal = Terminal(10, 3, DummyProcessInput())

    terminal.feed(b"\x1b[2026$p\x1b[2048$pX")
    screen = terminal.get_screen_content()

    assert screen["text"].splitlines()[0] == "X         "
    assert "p" not in screen["text"]


@pytest.mark.unit
def test_private_sgr_sequence_from_fullscreen_apps_does_not_crash():
    terminal = Terminal(10, 3, DummyProcessInput())

    terminal.feed(b"\x1b[?25;7mX")
    screen = terminal.get_screen_content()

    assert screen["text"].splitlines()[0] == "X         "


@pytest.mark.unit
def test_optional_float_setting_uses_default_when_missing():
    settings_manager = type(
        "SettingsManager",
        (),
        {"get_setting": lambda self, section, setting: ""},
    )()
    pty_driver = PtyDriver()

    assert (
        pty_driver._get_optional_float_setting(
            settings_manager,
            "screen",
            "ptyOutputTimeout",
            PTYConstants.OUTPUT_READ_TIMEOUT,
        )
        == PTYConstants.OUTPUT_READ_TIMEOUT
    )


@pytest.mark.unit
def test_pty_stdin_input_interrupts_output_when_all_keys_interrupt_enabled():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = True
    settings_manager.get_setting.return_value = ""
    output_manager = Mock()
    pty_driver.env = {
        "runtime": {
            "SettingsManager": settings_manager,
            "OutputManager": output_manager,
        }
    }

    pty_driver.interrupt_output_on_stdin_input(b"a")
    pty_driver.stdin_interrupt_thread.join(timeout=1.0)

    output_manager.interrupt_output.assert_called_once_with()


@pytest.mark.unit
def test_pty_stdin_input_interrupt_does_not_block_input_injection():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = True
    settings_manager.get_setting.return_value = ""
    interrupt_started = threading.Event()
    release_interrupt = threading.Event()

    def slow_interrupt():
        interrupt_started.set()
        release_interrupt.wait(timeout=1.0)

    output_manager = Mock(interrupt_output=Mock(side_effect=slow_interrupt))
    pty_driver.env = {
        "input": {"curr_input": []},
        "runtime": {
            "SettingsManager": settings_manager,
            "OutputManager": output_manager,
            "DebugManager": Mock(write_debug_out=Mock()),
        },
    }
    pty_driver.inject_text_to_screen = Mock()

    start_time = time.monotonic()
    pty_driver.handle_stdin_input(b"a", Mock())
    elapsed = time.monotonic() - start_time

    try:
        assert interrupt_started.wait(timeout=0.2)
        assert elapsed < 0.2
        pty_driver.inject_text_to_screen.assert_called_once_with(b"a")
    finally:
        release_interrupt.set()
        pty_driver.stdin_interrupt_thread.join(timeout=1.0)


@pytest.mark.unit
def test_pty_raw_tab_records_recent_tab_keypress():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = False
    input_manager = Mock()
    pty_driver.env = {
        "input": {"curr_input": []},
        "runtime": {
            "DebugManager": Mock(write_debug_out=Mock()),
            "InputManager": input_manager,
            "SettingsManager": settings_manager,
        },
    }
    pty_driver.inject_text_to_screen = Mock()

    pty_driver.handle_stdin_input(b"\t", Mock())

    input_manager.record_unmanaged_keypress.assert_called_once_with("KEY_TAB")
    pty_driver.inject_text_to_screen.assert_called_once_with(b"\t")


@pytest.mark.unit
def test_pty_plain_stdin_does_not_record_tab_keypress():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = False
    input_manager = Mock()
    pty_driver.env = {
        "input": {"curr_input": []},
        "runtime": {
            "DebugManager": Mock(write_debug_out=Mock()),
            "InputManager": input_manager,
            "SettingsManager": settings_manager,
        },
    }
    pty_driver.inject_text_to_screen = Mock()

    pty_driver.handle_stdin_input(b"a", Mock())

    input_manager.record_unmanaged_keypress.assert_not_called()
    pty_driver.inject_text_to_screen.assert_called_once_with(b"a")


@pytest.mark.unit
def test_pty_stdin_input_honors_interrupt_disabled():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = False
    output_manager = Mock()
    pty_driver.env = {
        "runtime": {
            "SettingsManager": settings_manager,
            "OutputManager": output_manager,
        }
    }

    pty_driver.interrupt_output_on_stdin_input(b"a")

    output_manager.interrupt_output.assert_not_called()


@pytest.mark.unit
def test_pty_stdin_input_leaves_filtered_interrupts_to_key_events():
    pty_driver = PtyDriver()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = True
    settings_manager.get_setting.return_value = "KEY_ENTER"
    output_manager = Mock()
    pty_driver.env = {
        "runtime": {
            "SettingsManager": settings_manager,
            "OutputManager": output_manager,
        }
    }

    pty_driver.interrupt_output_on_stdin_input(b"a")

    output_manager.interrupt_output.assert_not_called()


@pytest.mark.unit
def test_pty_backspace_with_fenrir_key_synthesizes_shortcut_events():
    pty_driver = PtyDriver()
    event_queue = Mock()
    pty_driver.env = {
        "input": {"curr_input": ["KEY_FENRIR"]},
    }

    handled = pty_driver.synthesize_backspace_shortcut(b"\x7f", event_queue)

    assert handled is True
    assert event_queue.put.call_count == 2
    first_event = event_queue.put.call_args_list[0].args[0]
    second_event = event_queue.put.call_args_list[1].args[0]
    assert first_event["Type"] == FenrirEventType.keyboard_input
    assert first_event["data"]["event_name"] == "KEY_BACKSPACE"
    assert first_event["data"]["event_state"] == 1
    assert second_event["data"]["event_state"] == 0


@pytest.mark.unit
def test_pty_plain_backspace_is_not_synthesized():
    pty_driver = PtyDriver()
    event_queue = Mock()
    pty_driver.env = {
        "input": {"curr_input": []},
    }

    handled = pty_driver.synthesize_backspace_shortcut(b"\x7f", event_queue)

    assert handled is False
    event_queue.put.assert_not_called()
