from unittest.mock import AsyncMock, call, patch

import pytest

from relay.tui.injector import TmuxInjector, _sanitize


class TestSanitize:
    def test_allows_safe_characters(self):
        assert _sanitize("alice:proj-1_v2.0 host", 64) == "alice:proj-1_v2.0 host"

    def test_strips_unsafe_characters(self):
        assert _sanitize("alice<script>alert(1)</script>", 64) == "alicescriptalert1script"

    def test_truncates_to_max_len(self):
        assert _sanitize("a" * 200, 10) == "a" * 10

    def test_strips_newlines_and_control_chars(self):
        assert _sanitize("alice\nmalicious\r\ninjection\t", 64) == "alicemaliciousinjection"

    def test_empty_string(self):
        assert _sanitize("", 64) == ""


class TestTmuxInjector:
    def test_init_stores_target_pane(self):
        injector = TmuxInjector("%42")
        assert injector.target_pane == "%42"

    @pytest.mark.asyncio
    async def test_inject_sends_literal_text_then_enter(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=0)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc) as mock_exec:
            result = await injector.inject("hello world")
            assert result is True
            assert mock_exec.call_count == 2
            type_args = mock_exec.call_args_list[0][0]
            assert type_args == ("tmux", "send-keys", "-t", "%1", "-l", "hello world")
            enter_args = mock_exec.call_args_list[1][0]
            assert enter_args == ("tmux", "send-keys", "-t", "%1", "Enter")

    @pytest.mark.asyncio
    async def test_inject_returns_false_on_type_failure(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=1)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc):
            result = await injector.inject("hello")
            assert result is False

    @pytest.mark.asyncio
    async def test_inject_returns_false_on_enter_failure(self):
        injector = TmuxInjector("%1")
        type_proc = AsyncMock()
        type_proc.wait = AsyncMock(return_value=0)
        enter_proc = AsyncMock()
        enter_proc.wait = AsyncMock(return_value=1)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", side_effect=[type_proc, enter_proc]):
            result = await injector.inject("hello")
            assert result is False

    @pytest.mark.asyncio
    async def test_inject_notification_formats_message(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=0)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc) as mock_exec:
            result = await injector.inject_notification("alice:proj", "team1")
            assert result is True
            type_args = mock_exec.call_args_list[0][0]
            text = type_args[5]
            assert "ALSO NOTF" in text
            assert "team1" in text
            assert "alice:proj" in text
            assert "read_new_messages" in text

    @pytest.mark.asyncio
    async def test_inject_notification_default_format(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=0)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc) as mock_exec:
            await injector.inject_notification("bob:work", "g1")
            type_args = mock_exec.call_args_list[0][0]
            text = type_args[5]
            assert "group 'g1'" in text
            assert "bob:work" in text

    @pytest.mark.asyncio
    async def test_inject_notification_sanitizes_from_display(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=0)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc) as mock_exec:
            await injector.inject_notification(
                "evil<script>alert('xss')</script>", "g1"
            )
            type_args = mock_exec.call_args_list[0][0]
            text = type_args[5]
            assert "<script>" not in text
            assert "evilscriptalertxssscript" in text

    @pytest.mark.asyncio
    async def test_inject_notification_sanitizes_group_id(self):
        injector = TmuxInjector("%1")
        mock_proc = AsyncMock()
        mock_proc.wait = AsyncMock(return_value=0)
        with patch("relay.tui.injector.asyncio.create_subprocess_exec", return_value=mock_proc) as mock_exec:
            await injector.inject_notification(
                "alice", "group\n; rm -rf /"
            )
            type_args = mock_exec.call_args_list[0][0]
            text = type_args[5]
            assert "\n" not in text
            assert ";" not in text
