from collections import namedtuple
import os
import shutil
import subprocess
import sys
import tempfile
import textwrap

import psutil
import pytest


def pytest_runtest_setup(item):
    sudo_marker = item.get_marker('sudo')
    if sudo_marker is not None:
        try:
            with open(os.devnull, 'w') as devnull:
                subprocess.check_call(
                    ['sudo', '-n', 'true'], stdout=devnull, stderr=devnull)
        except subprocess.CalledProcessError:
            pytest.skip('must be able to run sudo without a password')


PyScriptResult = namedtuple(
    'PyScriptResult', ['stdout', 'stderr', 'pid', 'returncode'])


class PyScript(object):

    def __init__(self, code, sudo=False):
        self.dirname = os.path.realpath(
            tempfile.mkdtemp(prefix='daemonocle_pytest_'))
        # This chmod is necessary for the setuid/setgid tests
        os.chmod(self.dirname, 0o711)
        self.basename = 'script.py'
        self.realpath = os.path.join(self.dirname, self.basename)
        with open(self.realpath, 'wb') as f:
            f.write(textwrap.dedent(code.lstrip('\n')).encode('utf-8'))
        self.sudo = sudo

    def run(self, *args):
        subenv = os.environ.copy()
        subenv['PYTHONUNBUFFERED'] = 'x'
        base_command = [sys.executable, self.realpath]
        if self.sudo:
            base_command = ['sudo', '-E'] + base_command
        proc = subprocess.Popen(
            base_command + list(args), cwd=self.dirname, env=subenv,
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stdout, stderr = proc.communicate()
        return PyScriptResult(stdout, stderr, proc.pid, proc.returncode)

    def teardown(self):
        procs = []
        for proc in psutil.process_iter():
            try:
                if (proc.exe() == sys.executable and
                        self.realpath in proc.cmdline()):
                    proc.terminate()
                    procs.append(proc)
            except (psutil.NoSuchProcess, psutil.AccessDenied, OSError):
                continue
        if psutil.wait_procs(procs, timeout=1)[1]:
            raise OSError('Failed to terminate subprocesses')
        shutil.rmtree(self.dirname)


@pytest.fixture(scope='function')
def pyscript(request):
    pfs = []

    def factory(code):
        pf = PyScript(code, sudo=hasattr(request.function, 'sudo'))
        pfs.append(pf)
        return pf

    def teardown():
        for pf in pfs:
            pf.teardown()

    request.addfinalizer(teardown)

    return factory