from __future__ import annotations

from unittest.mock import MagicMock, patch
import subprocess
import pytest

from actions.service import (
    handle_service_start,
    handle_service_stop,
    handle_service_restart,
    handle_service_status,
)

@patch("subprocess.run")
def test_service_start_success(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
    
    res = handle_service_start({"service": "nginx"})
    assert res["success"] is True
    assert "started successfully" in res["message"]
    
    mock_run.assert_called_once_with(
        ["systemctl", "start", "nginx"],
        capture_output=True,
        text=True,
        timeout=30,
        env={"LC_ALL": "C"},
        check=False
    )

@patch("subprocess.run")
def test_service_start_failure(mock_run):
    mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="Job failed")
    
    res = handle_service_start({"service": "nginx"})
    assert res["success"] is False
    assert res["error"] == "Job failed"

@patch("subprocess.run")
def test_service_start_timeout(mock_run):
    mock_run.side_effect = subprocess.TimeoutExpired(["systemctl", "start", "nginx"], timeout=30)
    
    res = handle_service_start({"service": "nginx"})
    assert res["success"] is False
    assert "command failed" in res["error"].lower()

def test_service_invalid_name_format():
    # Attempting to start service with shell character injection
    res = handle_service_start({"service": "nginx; rm -rf /"})
    assert res["success"] is False
    assert "Invalid service name format" in res["error"]

def test_service_not_whitelisted():
    # Attempting to start a service not in whitelist (e.g. systemd-udevd)
    res = handle_service_start({"service": "systemd-udevd"})
    assert res["success"] is False
    assert "not in the whitelist" in res["error"]

@patch("subprocess.run")
def test_service_stop_success(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
    
    res = handle_service_stop({"service": "mysql"})
    assert res["success"] is True
    assert "stopped successfully" in res["message"]
    
    mock_run.assert_called_once_with(
        ["systemctl", "stop", "mysql"],
        capture_output=True,
        text=True,
        timeout=30,
        env={"LC_ALL": "C"},
        check=False
    )

@patch("subprocess.run")
def test_service_restart_success(mock_run):
    mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
    
    res = handle_service_restart({"service": "redis"})
    assert res["success"] is True
    assert "restarted successfully" in res["message"]
    
    mock_run.assert_called_once_with(
        ["systemctl", "restart", "redis"],
        capture_output=True,
        text=True,
        timeout=30,
        env={"LC_ALL": "C"},
        check=False
    )

@patch("subprocess.run")
def test_service_status_active_running(mock_run):
    mock_run.return_value = MagicMock(
        returncode=0,
        stdout="ActiveState=active\nSubState=running\n",
        stderr=""
    )

    res = handle_service_status({"service": "nginx"})
    assert res["success"] is True
    assert res["status"] == "active"
    assert res["sub_state"] == "running"

    mock_run.assert_called_once_with(
        ["systemctl", "show", "--property=ActiveState,SubState,MainPID,MemoryCurrent,ActiveEnterTimestampMonotonic", "nginx"],
        capture_output=True,
        text=True,
        timeout=30,
        env={"LC_ALL": "C"},
        check=False
    )

@patch("subprocess.run")
def test_service_status_inactive_dead(mock_run):
    mock_run.return_value = MagicMock(
        returncode=0,
        stdout="ActiveState=inactive\nSubState=dead\n",
        stderr=""
    )

    res = handle_service_status({"service": "docker"})
    assert res["success"] is True
    assert res["status"] == "inactive"
    assert res["sub_state"] == "dead"
    # Detail fields absent from systemd output stay null — never fabricated.
    assert res["pid"] is None
    assert res["memory_bytes"] is None
    assert res["uptime_seconds"] is None


@patch("builtins.open", create=True)
@patch("subprocess.run")
def test_service_status_reports_pid_memory_uptime(mock_run, mock_open):
    mock_run.return_value = MagicMock(
        returncode=0,
        stdout=(
            "ActiveState=active\nSubState=running\nMainPID=1234\n"
            "MemoryCurrent=52428800\nActiveEnterTimestampMonotonic=90000000\n"
        ),
        stderr="",
    )
    handle = MagicMock()
    handle.readline.return_value = "100.00 380.00\n"
    mock_open.return_value.__enter__.return_value = handle

    res = handle_service_status({"service": "nginx"})
    assert res["success"] is True
    assert res["pid"] == 1234
    assert res["memory_bytes"] == 52428800
    # /proc/uptime 100s - 90s monotonic enter time = 10s active
    assert res["uptime_seconds"] == 10.0


@patch("subprocess.run")
def test_service_status_zero_pid_is_null(mock_run):
    mock_run.return_value = MagicMock(
        returncode=0,
        stdout="ActiveState=inactive\nSubState=dead\nMainPID=0\nMemoryCurrent=[not set]\n",
        stderr="",
    )
    res = handle_service_status({"service": "redis"})
    assert res["pid"] is None
    assert res["memory_bytes"] is None
