import importlib.util
import threading
import time
from pathlib import Path
from unittest.mock import Mock

import pytest

from fenrirscreenreader.core.outputManager import OutputManager


def build_output_manager():
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = True
    settings_manager.get_setting_as_float.return_value = 1.0
    sound_driver = Mock()
    speech_driver = Mock()
    output_manager = OutputManager()
    output_manager.env = {
        "soundIcons": {
            "ACCEPT": "/tmp/Accept.wav",
            "ERRORSCREEN": "/tmp/ErrorScreen.wav",
        },
        "runtime": {
            "SettingsManager": settings_manager,
            "SoundDriver": sound_driver,
            "SpeechDriver": speech_driver,
            "DebugManager": Mock(write_debug_out=Mock()),
        },
    }
    return output_manager, sound_driver, speech_driver


def load_key_interrupt_module():
    module_path = (
        Path(__file__).resolve().parents[2]
        / "src"
        / "fenrirscreenreader"
        / "commands"
        / "onKeyInput"
        / "10000-shut_up.py"
    )
    spec = importlib.util.spec_from_file_location(
        "fenrir_key_interrupt", module_path
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


@pytest.mark.unit
def test_present_text_allows_sound_only_feedback():
    output_manager, sound_driver, _speech_driver = build_output_manager()

    output_manager.present_text("", sound_icon="Accept", interrupt=False)

    sound_driver.play_sound_file.assert_called_once_with(
        "/tmp/Accept.wav", False
    )


@pytest.mark.unit
def test_play_sound_supports_error_alias():
    output_manager, sound_driver, _speech_driver = build_output_manager()

    assert output_manager.play_sound("Error") is True

    sound_driver.play_sound_file.assert_called_once_with(
        "/tmp/ErrorScreen.wav", True
    )


@pytest.mark.unit
def test_interrupt_output_async_does_not_block_on_slow_cancel():
    output_manager, _sound_driver, speech_driver = build_output_manager()
    interrupt_started = threading.Event()
    release_interrupt = threading.Event()

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

    speech_driver.cancel.side_effect = slow_cancel

    start_time = time.monotonic()
    output_manager.interrupt_output_async()
    elapsed = time.monotonic() - start_time

    try:
        assert interrupt_started.wait(timeout=0.2)
        assert elapsed < 0.2
        output_manager.interrupt_output_async()
        assert speech_driver.cancel.call_count == 1
    finally:
        release_interrupt.set()
        output_manager.interrupt_thread.join(timeout=1.0)


@pytest.mark.unit
def test_interrupt_output_waits_only_briefly_for_slow_cancel():
    output_manager, _sound_driver, speech_driver = build_output_manager()
    interrupt_started = threading.Event()
    release_interrupt = threading.Event()

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

    speech_driver.cancel.side_effect = slow_cancel

    start_time = time.monotonic()
    output_manager.interrupt_output()
    elapsed = time.monotonic() - start_time

    try:
        assert interrupt_started.wait(timeout=0.2)
        assert elapsed < 0.2
        output_manager.interrupt_output()
        assert speech_driver.cancel.call_count == 1
    finally:
        release_interrupt.set()
        output_manager.interrupt_thread.join(timeout=1.0)


@pytest.mark.unit
def test_key_interrupt_command_uses_nonblocking_interrupt():
    module = load_key_interrupt_module()
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.return_value = True
    settings_manager.get_setting.return_value = ""
    output_manager = Mock()
    env = {
        "input": {
            "curr_input": ["KEY_A"],
            "prev_input": [],
        },
        "runtime": {
            "InputManager": Mock(no_key_pressed=Mock(return_value=False)),
            "OutputManager": output_manager,
            "ScreenManager": Mock(is_screen_change=Mock(return_value=False)),
            "SettingsManager": settings_manager,
        },
    }
    command = module.command()
    command.initialize(env)

    command.run()

    output_manager.interrupt_output_async.assert_called_once_with()
    output_manager.interrupt_output.assert_not_called()
