import subprocess
from typing import Callable, Iterator, Sequence

from l4d2host.process import CommandResult, run_command


SYSTEMCTL_HELPER = "/usr/local/libexec/left4me/left4me-systemctl"
JOURNALCTL_HELPER = "/usr/local/libexec/left4me/left4me-journalctl"


def systemctl_command(action: str, name: str) -> list[str]:
    return ["sudo", "-n", SYSTEMCTL_HELPER, action, name]


def journalctl_command(name: str, lines: int = 200, follow: bool = True) -> list[str]:
    follow_arg = "--follow" if follow else "--no-follow"
    return ["sudo", "-n", JOURNALCTL_HELPER, name, "--lines", str(lines), follow_arg]


def enable_service(
    name: str,
    *,
    on_stdout: Callable[[str], None] | None = None,
    on_stderr: Callable[[str], None] | None = None,
    passthrough: bool = False,
    should_cancel: Callable[[], bool] | None = None,
) -> CommandResult:
    return run_command(
        systemctl_command("enable", name),
        on_stdout=on_stdout,
        on_stderr=on_stderr,
        passthrough=passthrough,
        should_cancel=should_cancel,
    )


def disable_service(
    name: str,
    *,
    on_stdout: Callable[[str], None] | None = None,
    on_stderr: Callable[[str], None] | None = None,
    passthrough: bool = False,
    should_cancel: Callable[[], bool] | None = None,
) -> CommandResult:
    return run_command(
        systemctl_command("disable", name),
        on_stdout=on_stdout,
        on_stderr=on_stderr,
        passthrough=passthrough,
        should_cancel=should_cancel,
    )


def show_service(name: str) -> CommandResult:
    return run_command(systemctl_command("show", name))


def stream_command(cmd: Sequence[str]) -> Iterator[str]:
    command = list(cmd)
    proc = subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
        bufsize=1,
    )
    try:
        if proc.stdout is None:
            return
        for raw in iter(proc.stdout.readline, ""):
            yield raw.rstrip("\n")
        returncode = proc.wait()
        if returncode is None:
            returncode = proc.poll()
        stderr = proc.stderr.read() if proc.stderr is not None else ""
        if returncode:
            raise subprocess.CalledProcessError(returncode=returncode, cmd=command, stderr=stderr)
    finally:
        if proc.poll() is None:
            proc.terminate()
            proc.wait(timeout=2)


def stream_journal(name: str, *, lines: int = 200, follow: bool = True) -> Iterator[str]:
    return stream_command(journalctl_command(name, lines=lines, follow=follow))
