"""
Tests for muxplex/muxterm.py — muxterm process supervision (start, restart, stop).
"""

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

import muxplex.muxterm as muxterm_mod
from muxplex.muxterm import (
    _find_muxterm_binary,
    start_muxterm,
    stop_muxterm,
)


# ---------------------------------------------------------------------------
# autouse fixture — reset module-level state between tests
# ---------------------------------------------------------------------------


@pytest.fixture(autouse=True)
def _reset_muxterm_state():
    """Reset module-level muxterm state before every test."""
    muxterm_mod._muxterm_process = None
    muxterm_mod._restart_task = None
    yield
    # Teardown: clean up again
    muxterm_mod._muxterm_process = None
    muxterm_mod._restart_task = None


# ---------------------------------------------------------------------------
# _find_muxterm_binary tests
# ---------------------------------------------------------------------------


def test_find_muxterm_binary_explicit_path(tmp_path):
    """Explicit binary path is returned as-is."""
    binary = tmp_path / "muxterm"
    binary.touch()
    binary.chmod(0o755)
    result = _find_muxterm_binary(str(binary))
    assert result == str(binary)


def test_find_muxterm_binary_uses_which():
    """Falls back to shutil.which('muxterm') when no explicit path."""
    with patch("shutil.which", return_value="/usr/bin/muxterm") as mock_which:
        result = _find_muxterm_binary()
        mock_which.assert_called_once_with("muxterm")
        assert result == "/usr/bin/muxterm"


def test_find_muxterm_binary_raises_when_not_found():
    """Raises FileNotFoundError if muxterm is not found."""
    with patch("shutil.which", return_value=None):
        with pytest.raises(FileNotFoundError):
            _find_muxterm_binary()


# ---------------------------------------------------------------------------
# start_muxterm tests
# ---------------------------------------------------------------------------


async def test_start_muxterm_spawns_process():
    """start_muxterm calls create_subprocess_exec with correct args and stores process."""
    mock_proc = MagicMock()
    mock_proc.returncode = None

    with (
        patch(
            "muxplex.muxterm._find_muxterm_binary",
            return_value="/usr/local/bin/muxterm",
        ),
        patch(
            "asyncio.create_subprocess_exec",
            new=AsyncMock(return_value=mock_proc),
        ) as mock_create,
    ):
        await start_muxterm("test-secret", port=7682, auto_restart=False)

    # Verify the binary path was passed as first arg
    mock_create.assert_called_once()
    call_args = mock_create.call_args
    assert call_args[0][0] == "/usr/local/bin/muxterm"
    assert "--addr" in call_args[0]
    assert "127.0.0.1:7682" in call_args[0]
    assert "--secret" in call_args[0]
    assert "test-secret" in call_args[0]

    # Verify process stored in module state
    assert muxterm_mod._muxterm_process is mock_proc


# ---------------------------------------------------------------------------
# stop_muxterm tests
# ---------------------------------------------------------------------------


async def test_stop_muxterm_terminates_process():
    """stop_muxterm terminates the process and sets state to None."""
    mock_proc = MagicMock()
    mock_proc.terminate = MagicMock()
    mock_proc.kill = MagicMock()
    mock_proc.wait = AsyncMock()

    muxterm_mod._muxterm_process = mock_proc

    await stop_muxterm()

    mock_proc.terminate.assert_called_once()
    assert muxterm_mod._muxterm_process is None


async def test_stop_muxterm_cancels_restart_task():
    """stop_muxterm cancels the restart task if active."""
    mock_proc = MagicMock()
    mock_proc.terminate = MagicMock()
    mock_proc.kill = MagicMock()
    mock_proc.wait = AsyncMock()

    mock_task = MagicMock()
    mock_task.cancel = MagicMock()

    muxterm_mod._muxterm_process = mock_proc
    muxterm_mod._restart_task = mock_task

    await stop_muxterm()

    mock_task.cancel.assert_called_once()
    assert muxterm_mod._restart_task is None


async def test_stop_muxterm_kills_on_timeout():
    """stop_muxterm kills the process if terminate doesn't finish in time."""
    mock_proc = MagicMock()
    mock_proc.terminate = MagicMock()
    mock_proc.kill = MagicMock()
    mock_proc.wait = AsyncMock(side_effect=asyncio.TimeoutError)

    muxterm_mod._muxterm_process = mock_proc

    await stop_muxterm()

    mock_proc.terminate.assert_called_once()
    mock_proc.kill.assert_called_once()
    assert muxterm_mod._muxterm_process is None
