#!/usr/bin/python3
#
# This is not a stable API; for use within autopkgtest only.
#
# Copyright 2024 Canonical Ltd.
# Copyright 2024 Simon McVittie
# SPDX-License-Identifier: GPL-2.0-or-later

import os
import shutil
import sys
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Sequence


class Dependency(ABC):
    @abstractmethod
    def check(self) -> bool:
        raise NotImplementedError


class Executable(Dependency):
    def __init__(
        self,
        exe: str,
        package: str,
        *,
        fatal: bool = True,
        if_not_root: bool = False,
        if_not_systemd: bool = False,
    ) -> None:
        self.exe = exe
        self.fatal = fatal
        self.package = package
        self.if_not_root = if_not_root
        self.if_not_systemd = if_not_systemd

    def check(self) -> bool:
        qualifiers: List[str] = []

        if self.if_not_root:
            if os.geteuid() == 0:
                return True

            qualifiers.append(' (not required if run as root)')

        if self.if_not_systemd:
            if Path('/run/systemd/system').exists():
                return True

            qualifiers.append(' (not required if init is systemd)')

        if shutil.which(self.exe):
            return True

        if self.fatal:
            level = 'ERROR'
        else:
            level = 'WARNING'

        sys.stderr.write(
            '%s: command %s not found, provided by package %s%s\n' %
            (level, self.exe, self.package, ''.join(qualifiers))
        )
        return not self.fatal


class FileDependency(Dependency):
    def __init__(
        self,
        path: Path,
        package: str,
        *,
        fatal: bool = True,
    ) -> None:
        self.fatal = fatal
        self.path = path
        self.package = package

    def check(self) -> bool:
        if self.path.exists():
            return True

        if self.fatal:
            level = 'ERROR'
        else:
            level = 'WARNING'

        sys.stderr.write(
            '%s: file %s not found, provided by package %s\n' %
            (level, self.path, self.package)
        )
        return not self.fatal


class KvmDependency(Dependency):
    def __init__(
        self,
        *,
        if_exists: bool,
    ) -> None:
        self.if_exists = if_exists

    def check(self) -> bool:
        if os.access('/dev/kvm', os.W_OK):
            return True

        if not os.path.exists('/dev/kvm'):
            if self.if_exists:
                return True
            else:
                sys.stderr.write('ERROR: /dev/kvm does not exist\n')
                return False

        sys.stderr.write('ERROR: no permission to write /dev/kvm\n')
        return False


def check_dependencies(dependencies: Sequence[Dependency]):
    ok = True

    # Intentionally not short-circuiting: we want to list all missing
    # dependencies at once
    for dep in dependencies:
        if not dep.check():
            ok = False

    return ok
