"""
Unit tests for incoming text announcement behavior.
"""

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

import pytest


def _load_incoming_module():
    module_path = (
        Path(__file__).resolve().parents[2]
        / "src"
        / "fenrirscreenreader"
        / "commands"
        / "onScreenUpdate"
        / "70000-incoming.py"
    )
    spec = importlib.util.spec_from_file_location(
        "fenrir_incoming_command", module_path
    )
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module


@pytest.fixture
def incoming_command():
    incoming_module = _load_incoming_module()
    output_manager = Mock(present_text=Mock())
    settings_manager = Mock()
    settings_manager.get_setting_as_bool.side_effect = (
        lambda section, setting: True
        if (section, setting) == ("speech", "auto_read_incoming")
        else False
    )
    settings_manager.get_setting_as_float.side_effect = (
        lambda section, setting: {
            ("speech", "rapid_update_window"): 0.3,
            ("speech", "batch_flush_interval"): 0.5,
        }[(section, setting)]
    )
    settings_manager.get_setting_as_int.side_effect = (
        lambda section, setting: {
            ("speech", "rapid_update_threshold"): 5,
            ("speech", "flood_line_threshold"): 500,
            ("speech", "max_batch_lines"): 100,
        }[(section, setting)]
    )

    env = {
        "runtime": {
            "OutputManager": output_manager,
            "SettingsManager": settings_manager,
            "ScreenManager": Mock(
                is_delta=Mock(return_value=True),
                is_screen_change=Mock(return_value=False),
            ),
        },
        "screen": {
            "new_delta": "",
            "new_delta_is_typing": False,
            "old_content_text": "",
            "new_content_text": "",
            "old_cursor": {"x": 0, "y": 0},
            "new_cursor": {"x": 0, "y": 0},
            "newNegativeDelta": "",
        },
        "commandBuffer": {},
    }

    command = incoming_module.command()
    command.initialize(env)
    return command, env, output_manager


@pytest.mark.unit
class TestIncomingCommand:
    """Test incoming text filtering for chat-like screen updates."""

    def test_prefers_inserted_chat_line_over_changed_headers(
        self, incoming_command
    ):
        command, env, output_manager = incoming_command
        env["screen"]["old_content_text"] = "\n".join(
            [
                "Status old",
                "Users old",
                "UserA: hi",
                "UserB: hello",
                "> ",
            ]
        )
        env["screen"]["new_content_text"] = "\n".join(
            [
                "Status new",
                "Users new",
                "UserB: hello",
                "UserC: test",
                "> ",
            ]
        )
        env["screen"]["new_delta"] = "\n".join(
            ["Status new", "Users new", "UserC: test"]
        )

        command.run()

        output_manager.present_text.assert_called_once_with(
            "UserC: test", interrupt=False, flush=False
        )

    def test_keeps_header_update_when_no_lower_screen_insert_exists(
        self, incoming_command
    ):
        command, env, output_manager = incoming_command
        env["screen"]["old_content_text"] = "\n".join(
            [
                "Status old",
                "Users old",
                "UserA: hi",
                "UserB: hello",
            ]
        )
        env["screen"]["new_content_text"] = "\n".join(
            [
                "Status new",
                "Users new",
                "UserA: hi",
                "UserB: hello",
            ]
        )
        env["screen"]["new_delta"] = "\n".join(["Status new", "Users new"])

        command.run()

        output_manager.present_text.assert_called_once_with(
            "Status new\nUsers new", interrupt=False, flush=False
        )

    def test_skips_typing_delta(self, incoming_command):
        command, env, output_manager = incoming_command
        env["screen"]["new_delta"] = "x"
        env["screen"]["new_delta_is_typing"] = True

        command.run()

        output_manager.present_text.assert_not_called()

    def test_dialog_button_paint_is_read_after_following_message(
        self, incoming_command
    ):
        command, env, output_manager = incoming_command
        env["screen"]["new_content_text"] = "\n".join(
            [
                "".ljust(20),
                "< OK >".center(20),
                "".ljust(20),
            ]
        )
        env["screen"]["new_delta"] = "\n".join(
            [
                " ",
                "       < OK >",
                " ",
            ]
        )

        command.run()

        output_manager.present_text.assert_not_called()

        env["screen"]["old_content_text"] = env["screen"]["new_content_text"]
        env["screen"]["new_content_text"] = "\n".join(
            [
                "This is a test".ljust(20),
                "< OK >".center(20),
                "".ljust(20),
            ]
        )
        env["screen"]["new_delta"] = "This is a test"

        command.run()

        output_manager.present_text.assert_called_once_with(
            "This is a test\n< OK >", interrupt=False, flush=False
        )

    def test_single_word_output_is_not_treated_as_dialog_button(
        self, incoming_command
    ):
        command, env, output_manager = incoming_command
        env["screen"]["new_content_text"] = "Ready"
        env["screen"]["new_delta"] = "Ready"

        command.run()

        output_manager.present_text.assert_called_once_with(
            "Ready", interrupt=False, flush=False
        )

    def test_dialog_button_is_not_preferred_over_message(
        self, incoming_command
    ):
        command, env, output_manager = incoming_command
        env["screen"]["old_content_text"] = "\n".join(
            [
                'bash-5.3$ dialog --msgbox "This is a test" -1 -1'.ljust(80),
                "".ljust(80),
                "".ljust(80),
                "".ljust(80),
                "".ljust(80),
                "".ljust(80),
            ]
        )
        env["screen"]["new_content_text"] = "\n".join(
            [
                "".ljust(80),
                "  This is a test".ljust(80),
                "".ljust(80),
                "".ljust(80),
                "                                  <  OK  >".ljust(80),
                "".ljust(80),
            ]
        )
        env["screen"]["new_delta"] = "\n".join(
            [
                " ",
                " This is a test ",
                " < OK > ",
            ]
        )

        command.run()

        output_manager.present_text.assert_called_once_with(
            " \n This is a test \n < OK > ",
            interrupt=False,
            flush=False,
        )
