'''
Let's make sure them docs work yah?

'''
from contextlib import contextmanager
import itertools
import os
import sys
import subprocess
import platform
import shutil
from typing import Callable

import pytest
import tractor
from tractor._testing import (
    examples_dir,
)

_non_linux: bool = platform.system() != 'Linux'
_friggin_macos: bool = platform.system() == 'Darwin'


@pytest.fixture
def run_example_in_subproc(
    loglevel: str,
    testdir: pytest.Pytester,
    reg_addr: tuple[str, int],
):

    @contextmanager
    def run(script_code):
        kwargs = dict()

        if platform.system() == 'Windows':
            # on windows we need to create a special __main__.py which will
            # be executed with ``python -m <modulename>`` on windows..
            shutil.copyfile(
                examples_dir() / '__main__.py',
                str(testdir / '__main__.py'),
            )

            # drop the ``if __name__ == '__main__'`` guard onwards from
            # the *NIX version of each script
            windows_script_lines = itertools.takewhile(
                lambda line: "if __name__ ==" not in line,
                script_code.splitlines()
            )
            script_code = '\n'.join(windows_script_lines)
            script_file = testdir.makefile('.py', script_code)

            # without this, tests hang on windows forever
            kwargs['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP

            # run the testdir "libary module" as a script
            cmdargs = [
                sys.executable,
                '-m',
                # use the "module name" of this "package"
                'test_example'
            ]
        else:
            script_file = testdir.makefile('.py', script_code)
            cmdargs = [
                sys.executable,
                str(script_file),
            ]

        # XXX: BE FOREVER WARNED: if you enable lots of tractor logging
        # in the subprocess it may cause infinite blocking on the pipes
        # due to backpressure!!!
        proc = testdir.popen(
            cmdargs,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            **kwargs,
        )
        assert not proc.returncode
        yield proc
        proc.wait()
        assert proc.returncode == 0

    yield run


@pytest.mark.parametrize(
    'example_script',

    # walk yields: (dirpath, dirnames, filenames)
    [
        (p[0], f)
        for p in os.walk(examples_dir())
        for f in p[2]

        if (
            '__' not in f  # ignore any pkg-mods
            # ignore any `__pycache__` subdir
            and '__pycache__' not in str(p[0])
            and f[0] != '_'  # ignore any WIP "examplel mods"
            and 'debugging' not in p[0]
            and 'integration' not in p[0]
            and 'advanced_faults' not in p[0]
            and 'multihost' not in p[0]
            and 'trio' not in p[0]
        )
    ],
    ids=lambda t: t[1],
)
def test_example(
    run_example_in_subproc: Callable,
    example_script: str,
    test_log: tractor.log.StackLevelAdapter,
    ci_env: bool,
):
    '''
    Load and run scripts from this repo's ``examples/`` dir as a user
    would copy and pasing them into their editor.

    On windows a little more "finessing" is done to make
    ``multiprocessing`` play nice: we copy the ``__main__.py`` into the
    test directory and invoke the script as a module with ``python -m
    test_example``.

    '''
    ex_file: str = os.path.join(*example_script)

    if (
        'rpc_bidir_streaming' in ex_file
        and
        sys.version_info < (3, 9)
    ):
        pytest.skip("2-way streaming example requires py3.9 async with syntax")

    if (
        'full_fledged_streaming_service' in ex_file
        and
        _friggin_macos
        and
        ci_env
    ):
        pytest.skip(
            'Streaming example is too flaky in CI\n'
            'AND their competitor runs this CI service..\n'
            'This test does run just fine "in person" however..'
        )

    if (
        'uds_transport_actor_tree' in ex_file
        and
        _friggin_macos
        and
        ci_env
    ):
        pytest.skip(
            'UDS-transport example reliably fails on macOS CI.\n'
            'UDS-on-macOS is otherwise un-exercised by the matrix\n'
            '(no `tpt_proto=uds` macOS job), so this new example is\n'
            'the first to surface it; the macOS UDS path needs\n'
            'root-causing. Passes on Linux.'
        )

    from .conftest import cpu_perf_headroom

    timeout: float = (
        60
        if ci_env and _non_linux
        else 16
    )

    # add latency headroom for CPU freq scaling/throttle
    # (auto-cpufreq et al.)
    headroom: float = cpu_perf_headroom()
    if headroom != 1.:
        timeout *= headroom

    with open(ex_file, 'r') as ex:
        code = ex.read()

        with run_example_in_subproc(code) as proc:
            err = None
            try:
                if not proc.poll():
                    _, err = proc.communicate(timeout=timeout)

            except subprocess.TimeoutExpired as e:
                test_log.exception(
                    f'Example failed to finish within {timeout}s ??\n'
                )
                proc.kill()
                err = e.stderr

            # if we get some gnarly output let's aggregate and raise
            if err:
                errmsg = err.decode()
                errlines = errmsg.splitlines()
                last_error = errlines[-1]
                if (
                    'Error' in last_error

                    # XXX: currently we print this to console, but maybe
                    # shouldn't eventually once we figure out what's
                    # a better way to be explicit about aio side
                    # cancels?
                    and
                    'asyncio.exceptions.CancelledError' not in last_error
                ):
                    raise Exception(errmsg)

            assert proc.returncode == 0
