import signal
import subprocess
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"
    assert result["requires_root"] is True


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)"


def test_terminate_process_blocks_system_critical_process(monkeypatch: Any) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=1)

    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": False,
            "reason": "System process (PID 1)",
            "is_critical": True,
        },
    )

    result = terminator.terminate_process(1)

    assert result["success"] is False
    assert result["message"] == "Cannot kill: System process (PID 1)"


def test_get_ai_warnings_handles_non_ai_process(monkeypatch: Any) -> None:
    terminator = ProcessTerminator(ai_process_monitor=_FakeAIProcessMonitor())
    fake_proc = _FakeProcess()

    monkeypatch.setattr(terminator.ai_monitor, "is_ai_process", lambda *_args: False)

    warnings = terminator._get_ai_warnings(fake_proc)

    assert warnings["has_warnings"] is False
    assert warnings["warnings"] == []
    assert warnings["data_loss_risk"] == "low"


def test_terminate_process_with_elevation_reports_missing_sudo_binary(
    monkeypatch: Any,
) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=222)

    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",
        },
    )
    monkeypatch.setattr(
        terminator,
        "_resolve_executable",
        lambda command: None if command == "sudo" else "/bin/kill",
    )

    result = terminator.terminate_process_with_elevation(
        222, signal.SIGTERM, sudo_password="topsecret"
    )

    assert result["success"] is False
    assert result["elevation_attempted"] is False
    assert "unable to escalate" in result["message"].lower()


def test_terminate_process_with_elevation_uses_prompt_sudo(
    monkeypatch: Any,
) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=666)
    captured: dict[str, Any] = {}

    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",
        },
    )
    monkeypatch.setattr(
        terminator,
        "_is_system_critical",
        lambda _proc: {"safe_to_kill": True, "reason": "", "is_critical": False},
    )
    monkeypatch.setattr(
        terminator,
        "_resolve_executable",
        lambda command: "/usr/bin/sudo" if command == "sudo" else "/bin/kill",
    )

    def _fake_run(
        command: list[str],
        check: bool,
        capture_output: bool,
        text: bool,
        input: str | None,
        timeout: float,
    ) -> subprocess.CompletedProcess[str]:
        captured["command"] = command
        captured["input"] = input
        assert check is False
        assert capture_output is True
        assert text is True
        assert timeout == 5.0
        return subprocess.CompletedProcess(command, returncode=0, stdout="", stderr="")

    monkeypatch.setattr(killer_module.subprocess, "run", _fake_run)

    result = terminator.terminate_process_with_elevation(
        666, signal.SIGTERM, sudo_password="topsecret"
    )

    assert result["success"] is True
    assert captured["command"] == [
        "/usr/bin/sudo",
        "-S",
        "-p",
        "",
        "--",
        "/bin/kill",
        "-s",
        str(int(signal.SIGTERM)),
        "666",
    ]
    assert captured["input"] == "topsecret\n"


def test_terminate_process_with_elevation_requires_password(
    monkeypatch: Any,
) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=667)

    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",
        },
    )
    monkeypatch.setattr(
        terminator,
        "_is_system_critical",
        lambda _proc: {"safe_to_kill": True, "reason": "", "is_critical": False},
    )
    monkeypatch.setattr(
        terminator,
        "_resolve_executable",
        lambda command: "/usr/bin/sudo" if command == "sudo" else "/bin/kill",
    )

    result = terminator.terminate_process_with_elevation(667, signal.SIGTERM)

    assert result["success"] is False
    assert result["elevation_attempted"] is True
    assert result["message"] == "Sudo password required"


def test_terminate_process_with_elevation_reports_incorrect_password(
    monkeypatch: Any,
) -> None:
    terminator = ProcessTerminator()
    fake_proc = _FakeProcess(pid=777)

    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",
        },
    )
    monkeypatch.setattr(
        terminator,
        "_is_system_critical",
        lambda _proc: {"safe_to_kill": True, "reason": "", "is_critical": False},
    )
    monkeypatch.setattr(
        terminator,
        "_resolve_executable",
        lambda command: "/usr/bin/sudo" if command == "sudo" else "/bin/kill",
    )

    def _fake_run(
        command: list[str],
        check: bool,
        capture_output: bool,
        text: bool,
        input: str | None,
        timeout: float,
    ) -> subprocess.CompletedProcess[str]:
        del command, check, capture_output, text, input, timeout
        return subprocess.CompletedProcess(
            args=["sudo", "kill"],
            returncode=1,
            stdout="",
            stderr="Sorry, try again.",
        )

    monkeypatch.setattr(killer_module.subprocess, "run", _fake_run)

    result = terminator.terminate_process_with_elevation(
        777, signal.SIGTERM, sudo_password="wrong"
    )

    assert result["success"] is False
    assert result["elevation_attempted"] is True
    assert result["message"] == "Incorrect sudo password"
