"""Tests for rathole.py - binary management and process lifecycle."""

from __future__ import annotations

import signal
from pathlib import Path
from unittest.mock import MagicMock, call, patch

import pytest

from cli import rathole as rathole_mgr
from cli.exceptions import BeaconSystemError


# ---------------------------------------------------------------------------
# write_client_config
# ---------------------------------------------------------------------------


class TestWriteClientConfig:
    def test_generates_valid_toml(self, state_dir):
        path = rathole_mgr.write_client_config(
            beacon_id="abc12345",
            server="1.2.3.4",
            control_port=2333,
            token="my-secret",
            local_port=51820,
        )
        content = path.read_text()
        assert "[client]" in content
        assert "remote_addr = \"1.2.3.4:2333\"" in content
        assert "token = \"my-secret\"" in content
        assert "local_addr = \"127.0.0.1:51820\"" in content
        assert 'type = "udp"' in content

    def test_config_file_named_by_beacon_id(self, state_dir):
        path = rathole_mgr.write_client_config(
            beacon_id="mybeaconid",
            server="1.2.3.4",
            control_port=2333,
            token="t",
            local_port=51820,
        )
        assert "mybeaconid" in path.name

    def test_service_name_uses_full_id(self, state_dir):
        path = rathole_mgr.write_client_config(
            beacon_id="58",
            server="1.2.3.4",
            control_port=2333,
            token="t",
            local_port=51820,
        )
        content = path.read_text()
        # Service name must match server format: beacon{id} (no dash, no truncation)
        assert "beacon58" in content
        assert "beacon-" not in content


# ---------------------------------------------------------------------------
# is_running
# ---------------------------------------------------------------------------


class TestIsRunning:
    def test_returns_false_when_no_pid_file(self, state_dir):
        assert rathole_mgr.is_running("ghost-beacon") is False

    def test_returns_true_when_pid_alive(self, state_dir):
        from cli.config import write_pid

        write_pid("alive-beacon", 1)  # PID 1 (init) is always running
        with patch("psutil.pid_exists", return_value=True), \
             patch("psutil.Process") as mock_proc:
            mock_proc.return_value.status.return_value = "running"
            assert rathole_mgr.is_running("alive-beacon") is True

    def test_returns_false_when_pid_dead(self, state_dir):
        from cli.config import write_pid

        write_pid("dead-beacon", 99999)
        with patch("psutil.pid_exists", return_value=False):
            assert rathole_mgr.is_running("dead-beacon") is False


# ---------------------------------------------------------------------------
# stop
# ---------------------------------------------------------------------------


class TestStop:
    def test_stop_terminates_process(self, state_dir):
        from cli.config import write_pid

        write_pid("stop-test", 12345)
        mock_proc = MagicMock()
        with patch("psutil.Process", return_value=mock_proc):
            result = rathole_mgr.stop("stop-test")
        mock_proc.terminate.assert_called_once()
        assert result is True

    def test_stop_returns_false_when_not_running(self, state_dir):
        result = rathole_mgr.stop("not-running")
        assert result is False

    def test_stop_kills_if_terminate_times_out(self, state_dir):
        import psutil
        from cli.config import write_pid

        write_pid("slow-process", 12345)
        mock_proc = MagicMock()
        mock_proc.wait.side_effect = psutil.TimeoutExpired(12345, 5)
        with patch("psutil.Process", return_value=mock_proc):
            rathole_mgr.stop("slow-process")
        mock_proc.kill.assert_called_once()


# ---------------------------------------------------------------------------
# _binary_name
# ---------------------------------------------------------------------------


class TestBinaryName:
    @pytest.mark.parametrize(
        "arch, os_name, expected_fragment",
        [
            ("x86_64", "linux", "x86_64-unknown-linux-gnu"),
            ("aarch64", "linux", "aarch64-unknown-linux-musl"),   # musl, not gnu
            ("arm", "linux", "armv7-unknown-linux-musleabihf"),
            ("x86_64", "darwin", "x86_64-apple-darwin"),
        ],
    )
    def test_binary_name_fragments(self, arch, os_name, expected_fragment):
        name = rathole_mgr._binary_name(arch, os_name)
        assert expected_fragment in name
        assert name.endswith(".zip")

    def test_unsupported_platform_raises(self):
        from cli.exceptions import BeaconSystemError
        with pytest.raises(BeaconSystemError, match="Unsupported platform"):
            rathole_mgr._binary_name("riscv64", "linux")
