import signal
from typing import Any

import psutil

import aitop.core.process.killer as killer_module
from aitop.core.process.killer import ProcessTerminator


class _FakeProcess:
    def __init__(self, pid: int = 123) -> None:
        self.pid = pid
        self.sent_signal: int | None = None

    def send_signal(self, signal_num: int) -> None:
        self.sent_signal = signal_num

    def wait(self, timeout: float) -> int:
        return 0

    def name(self) -> str:
        return "python"

    def cmdline(self) -> list[str]:
        return ["python", "train.py", "--distributed", "--serve"]

    def parent(self) -> Any:
        return None


class _FakeAIProcessMonitor:
    def is_ai_process(self, proc_name: str, cmdline: str) -> bool:
        del proc_name, cmdline
        return True


def test_terminate_process_returns_missing_pid_error(monkeypatch: Any) -> None:
    terminator = ProcessTerminator()
    monkeypatch.setattr(
        terminator,
        "_verify_process_exists",
        lambda _pid: {
            "exists": False,
            "accessible": False,
            "is_zombie": False,
            "message": "PID 999 does not exist",
        },
    )

    result = terminator.terminate_process(999)

    assert result["success"] is False
    assert result["message"] == "PID 999 does not exist"
    assert "PID 999 does not exist" in result["errors"]


def test_terminate_process_blocks_when_permissions_fail(monkeypatch: Any) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess()

    monkeypatch.setattr(
        terminator,
        "_verify_process_exists",
        lambda _pid: {
            "exists": True,
            "accessible": True,
            "is_zombie": False,
            "message": "ok",
        },
    )
    monkeypatch.setattr(
        killer_module.psutil,
        "Process",
        lambda _pid: fake_proc,
    )
    monkeypatch.setattr(
        terminator,
        "_check_permissions",
        lambda _proc: {
            "can_terminate": False,
            "requires_root": True,
            "reason": "not owner",
        },
    )

    result = terminator.terminate_process(123)

    assert result["success"] is False
    assert result["message"] == "Root/administrator privileges required"


def test_terminate_process_success_records_signal(monkeypatch: Any) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=321)

    monkeypatch.setattr(
        terminator,
        "_verify_process_exists",
        lambda _pid: {
            "exists": True,
            "accessible": True,
            "is_zombie": False,
            "message": "ok",
        },
    )
    monkeypatch.setattr(killer_module.psutil, "Process", lambda _pid: fake_proc)
    monkeypatch.setattr(
        terminator,
        "_check_permissions",
        lambda _proc: {"can_terminate": True, "requires_root": False, "reason": "ok"},
    )
    monkeypatch.setattr(
        terminator,
        "_is_system_critical",
        lambda _proc: {"safe_to_kill": True, "reason": "", "is_critical": False},
    )

    result = terminator.terminate_process(321, signal.SIGTERM)

    assert result["success"] is True
    assert result["signal_sent"] == "SIGTERM"
    assert "exit code 0" in result["message"]
    assert fake_proc.sent_signal == signal.SIGTERM


def test_verify_process_exists_handles_zombie_exception(monkeypatch: Any) -> None:
    terminator = ProcessTerminator()

    class _ZombieProcess:
        def status(self) -> str:
            raise psutil.ZombieProcess(42)

    monkeypatch.setattr(killer_module.psutil, "pid_exists", lambda _pid: True)
    monkeypatch.setattr(killer_module.psutil, "Process", lambda _pid: _ZombieProcess())

    info = terminator._verify_process_exists(42)

    assert info["is_zombie"] is True
    assert info["message"] == "Process is a zombie"


def test_get_ai_warnings_reports_high_risk_training_distributed() -> None:
    terminator = ProcessTerminator(ai_process_monitor=_FakeAIProcessMonitor())
    fake_proc = _FakeProcess()

    warnings = terminator._get_ai_warnings(fake_proc)

    assert warnings["has_warnings"] is True
    assert warnings["data_loss_risk"] == "high"
    assert any("Training process" in item for item in warnings["warnings"])
    assert any("distributed job" in item for item in warnings["warnings"])


def test_is_system_critical_flags_non_user_service_with_init_parent() -> None:
    terminator = ProcessTerminator()

    class _Parent:
        pid = 1

    class _ServiceProcess:
        pid = 456

        def name(self) -> str:
            return "daemon-service"

        def parent(self) -> _Parent:
            return _Parent()

    info = terminator._is_system_critical(_ServiceProcess())

    assert info["safe_to_kill"] is False
    assert info["is_critical"] is True
    assert info["reason"] == "System service (parent is init/systemd)"
