"""Tests for cross-platform compatibility module."""

import asyncio
import os
import signal
import subprocess
import sys
import time
from unittest.mock import MagicMock, patch

import psutil
import pytest

from skop_runner.compat import (
    get_default_system_path,
    get_subprocess_kwargs,
    is_pid_alive,
    kill_process_tree,
    secure_file_permissions,
    setup_signal_handlers,
    terminate_process,
)


# --- is_pid_alive ---


class TestIsPidAlive:
    def test_current_process_is_alive(self):
        assert is_pid_alive(os.getpid()) is True

    def test_dead_process_is_not_alive(self):
        # PID 0 is the kernel on Unix, use a very high unlikely PID
        assert is_pid_alive(999999999) is False


# --- kill_process_tree ---


class TestKillProcessTree:
    def test_nonexistent_pid_no_error(self):
        kill_process_tree(999999999)

    def test_kills_single_process(self):
        proc = subprocess.Popen(
            [sys.executable, "-c", "import time; time.sleep(60)"],
            start_new_session=True,
        )
        assert psutil.pid_exists(proc.pid)

        kill_process_tree(proc.pid, timeout=3)
        proc.wait(timeout=3)

        assert proc.returncode is not None
        assert not psutil.pid_exists(proc.pid)


# --- terminate_process ---


class TestTerminateProcess:
    def test_terminates_running_process(self):
        proc = subprocess.Popen(
            [sys.executable, "-c", "import time; time.sleep(60)"]
        )
        terminate_process(proc.pid, timeout=5)
        proc.wait(timeout=5)
        assert proc.returncode is not None

    def test_nonexistent_pid_no_error(self):
        terminate_process(999999999)


# --- get_subprocess_kwargs ---


class TestGetSubprocessKwargs:
    @patch("skop_runner.compat.sys")
    def test_unix_returns_start_new_session(self, mock_sys):
        mock_sys.platform = "linux"
        result = get_subprocess_kwargs()
        assert result == {"start_new_session": True}

    @patch("skop_runner.compat.sys")
    def test_darwin_returns_start_new_session(self, mock_sys):
        mock_sys.platform = "darwin"
        result = get_subprocess_kwargs()
        assert result == {"start_new_session": True}

    @patch("skop_runner.compat.sys")
    def test_windows_returns_creation_flags(self, mock_sys):
        mock_sys.platform = "win32"
        result = get_subprocess_kwargs()
        assert "creationflags" in result
        # CREATE_NEW_PROCESS_GROUP = 0x00000200
        assert result["creationflags"] == getattr(
            subprocess, "CREATE_NEW_PROCESS_GROUP", 0x00000200
        )

    def test_current_platform_returns_dict(self):
        result = get_subprocess_kwargs()
        assert isinstance(result, dict)
        assert len(result) == 1


# --- setup_signal_handlers ---


class TestSetupSignalHandlers:
    @patch("skop_runner.compat.sys")
    def test_unix_uses_add_signal_handler(self, mock_sys):
        mock_sys.platform = "linux"
        mock_loop = MagicMock()
        handler = MagicMock()

        setup_signal_handlers(mock_loop, handler)

        assert mock_loop.add_signal_handler.call_count == 2
        mock_loop.add_signal_handler.assert_any_call(signal.SIGINT, handler)
        mock_loop.add_signal_handler.assert_any_call(signal.SIGTERM, handler)

    @patch("skop_runner.compat.signal")
    @patch("skop_runner.compat.sys")
    def test_windows_uses_signal_signal(self, mock_sys, mock_signal):
        mock_sys.platform = "win32"
        mock_signal.SIGINT = signal.SIGINT
        mock_signal.SIGBREAK = getattr(signal, "SIGBREAK", 21)
        mock_loop = MagicMock()
        handler = MagicMock()

        setup_signal_handlers(mock_loop, handler)

        assert mock_signal.signal.call_count >= 1
        # First call should be for SIGINT
        first_call = mock_signal.signal.call_args_list[0]
        assert first_call[0][0] == signal.SIGINT


# --- secure_file_permissions ---


class TestSecureFilePermissions:
    @pytest.mark.skipif(sys.platform == "win32", reason="Unix permissions")
    def test_unix_sets_600_permissions(self, tmp_path):
        test_file = tmp_path / "secret.txt"
        test_file.write_text("secret")

        secure_file_permissions(str(test_file))

        import stat as stat_mod
        mode = stat_mod.S_IMODE(os.stat(test_file).st_mode)
        assert mode == 0o600

    @patch("skop_runner.compat.subprocess")
    @patch("skop_runner.compat.sys")
    def test_windows_calls_icacls(self, mock_sys, mock_subprocess):
        mock_sys.platform = "win32"
        with patch.dict(os.environ, {"USERNAME": "testuser"}):
            secure_file_permissions("/fake/path")

        mock_subprocess.run.assert_called_once()
        args = mock_subprocess.run.call_args[0][0]
        assert args[0] == "icacls"
        assert "/inheritance:r" in args

    @patch("skop_runner.compat.subprocess")
    @patch("skop_runner.compat.sys")
    def test_windows_empty_username_skips(self, mock_sys, mock_subprocess):
        mock_sys.platform = "win32"
        with patch.dict(os.environ, {"USERNAME": ""}, clear=False):
            secure_file_permissions("/fake/path")

        mock_subprocess.run.assert_not_called()


# --- get_default_system_path ---


class TestGetDefaultSystemPath:
    @patch("skop_runner.compat.sys")
    def test_unix_returns_unix_paths(self, mock_sys):
        mock_sys.platform = "linux"
        result = get_default_system_path()
        assert "/usr/bin" in result
        assert ":" in result

    @patch("skop_runner.compat.sys")
    def test_windows_returns_windows_paths(self, mock_sys):
        mock_sys.platform = "win32"
        with patch.dict(os.environ, {"SystemRoot": r"C:\Windows"}):
            result = get_default_system_path()
        assert "System32" in result
        assert ";" in result

    def test_current_platform_returns_string(self):
        result = get_default_system_path()
        assert isinstance(result, str)
        assert len(result) > 0
