"""
Generate missing integration patch tests.

This scripts scans the ddtrace/contrib folder for all the currently supported
integrations that implement the patch method in the patch submodule, and checks
that there is a corresponding patch test is implemented in tests/contrib. It
then generates a basic patch test for those integrations that do not have one.

Run as

    python scripts/contrib-patch-tests.py

from the repository root folder to automatically (re-)generate missing
integration patch tests.
"""

import ast
from pathlib import Path
from textwrap import dedent


CONTRIB_PATH = Path.cwd() / "ddtrace" / "contrib"
CONTRIB_TEST_PATH = Path().cwd() / "tests" / "contrib"


def generate_patch_test_source(contrib):
    print(f"Generating patch test for {contrib}")

    # Parse the contrib module to get the ``required_modules`` list
    # DEV: Parsing the module is much lighter and safer than importing it
    module_ast = ast.parse((CONTRIB_PATH / contrib / "__init__.py").read_text())
    # require modules must be defined as module level array
    required_modules_node = [
        _ for _ in module_ast.body if isinstance(_, ast.Assign) and _.targets[0].id == "required_modules"
    ]
    if not required_modules_node:
        print(
            f"WARNING: failed to generate patch test for {contrib},"
            f" required_modules not found in ddtrace.contrib.{contrib}.__init__"
        )
        return

    required_modules = [_.value for _ in required_modules_node[0].value.elts]

    if not required_modules:
        print(
            f"WARNING: failed to generate patch test for {contrib},"
            f" ddtrace.contrib.{contrib}.required_modules could not be parsed"
        )
        return

    # We use the first entry in the ``required_modules`` for the module name.
    module = required_modules[0]

    (CONTRIB_TEST_PATH / contrib / f"test_{contrib}_patch_generated.py").write_text(
        dedent(
            f"""
            # This test script was automatically generated by the contrib-patch-tests.py
            # script. If you want to make changes to it, you should make sure that you have
            # removed the ``_generated`` suffix from the file name, to prevent the content
            # from being overwritten by future re-generations.

            from ddtrace.contrib.{contrib} import get_version
            from ddtrace.contrib.{contrib}.patch import patch


            try:
                from ddtrace.contrib.{contrib}.patch import unpatch
            except ImportError:
                unpatch = None
            from tests.contrib.patch import PatchTestCase


            class Test{contrib.title()}Patch(PatchTestCase.Base):
                __integration_name__ = "{contrib}"
                __module_name__ = "{module}"
                __patch_func__ = patch
                __unpatch_func__ = unpatch
                __get_version__ = get_version

                def assert_module_patched(self, {module.replace(".", "_")}):
                    pass

                def assert_not_module_patched(self, {module.replace(".", "_")}):
                    pass

                def assert_not_module_double_patched(self, {module.replace(".", "_")}):
                    pass
            """
        ).lstrip()
    )


# Take all the contribs that have a patch submodule. We assume they implement
# the patch API by exposing a ``patch`` function.
for contrib in [p.parent.name for p in CONTRIB_PATH.glob("*/patch.py")]:
    if not any((CONTRIB_TEST_PATH / contrib / f).is_file() for f in ("test_patch.py", f"test_{contrib}_patch.py")):
        generate_patch_test_source(contrib)
