"""Contains the function that will generate the __init__.py file for the ogn/tests directory."""
import hashlib
import sys
from importlib import import_module
from pathlib import Path
from typing import List, Optional

from .nodes import NodeGenerationError
from .ThreadsafeOpen import ThreadsafeOpen
from .utils import logger


# ==============================================================================================================
def import_tests_in_directory(module_file: str, module_name: str):
    """Find all of the .ogn-generated tests in a module's directory and import them into that module.
    This will only be called from the generated test directory __init__.py file, generated below by the
    import_file_contents() function

    Args:
        module_file: Full path of the __init__.py file for the generated test directory (e.g. its __file__)
        module_name: Module name at which the generated test directory is imported (e.g. its __name__)
    """
    this_dir = Path(module_file).parent
    this_module = sys.modules[module_name]
    test_module_files = this_dir.glob("Test*.py")
    for test_module_file in test_module_files:
        test_module_name = test_module_file.stem
        try:
            test_module = import_module(f".{test_module_name}", module_name)
            setattr(this_module, test_module_name, test_module.TestOgn)
        except Exception as error:  # noqa: PLW0703
            logger.warning(
                "Failed to import test %s in module %s (%s) - skipping.", test_module_name, module_name, error
            )


# ==============================================================================================================
def import_file_contents(file_to_write: Optional[Path] = None) -> List[str]:
    """Returns the contents of the tests/__init__.py file that imports the given list of test classes.
    If a file_to_write is passed in then the contents are written there before returning.
    Not part of the interface, but not hidden with a dunder so that a test can be written against it."""

    # To enable easier upgrades the test directory initialization mostly happens in a utility function
    file_contents = [
        '"""====== GENERATED BY omni.graph.tools - DO NOT EDIT ======"""',
        "import omni.graph.tools as ogt",
        "ogt.import_tests_in_directory(__file__, __name__)",
    ]
    # Use the md5 to avoid overwriting the file multiple times, which could trigger extension reload
    expected_md5 = hashlib.md5(bytes("".join([f"{line}\n" for line in file_contents]), "utf-8")).hexdigest()

    if file_to_write is not None:
        import_directory = file_to_write.parent
        # First ensure that the directory in which the file lives exists
        if not import_directory.exists():
            try:
                import_directory.mkdir(mode=0o777, parents=True, exist_ok=True)
            except Exception as error:
                raise NodeGenerationError(f"Cannot create test directory {import_directory}") from error

        # If the path is not a directory then there is a serious problem that cannot be fixed safely here.
        if not import_directory.is_dir():
            raise NodeGenerationError(f"Cannot write __init__.py file to non-directory {import_directory}")

        if file_to_write.exists():
            with open(file_to_write, "rb") as f:
                if hashlib.md5(f.read()).hexdigest() == expected_md5:
                    return []

        # ThreadsafeOpen will report a warning if there's a problem
        with ThreadsafeOpen(file_to_write, "w", newline="\n") as test_init_fd:
            test_init_fd.writelines([f"{line}\n" for line in file_contents])

    return file_contents


# ==============================================================================================================
def ensure_test_is_imported(test_class_name: str, test_directory: Path):
    """Reads the tests __init__.py file and verifies that a test file is imported in it

    Args:
        test_class_name: Name of the test class, which is also the name of the file it lives in
        test_directory: Directory in which the test file lives and where the import will be added if necessary

    Raises:
        NodeGenerationError if there was a problem adding the test import
    """
    try:
        test_init_file = test_directory / "__init__.py"
        needs_regeneration = True
        if test_init_file.exists():
            # For backward compatibility - check to see if the file in place is the latest version.
            # If it is then just leave it, otherwise flag it for regeneration.
            with open(test_init_file, "r", encoding="utf-8") as init_fd:
                needs_regeneration = init_fd.readline().find("GENERATED") < 0

        # If anything is out of date the file has to be rewritten
        if needs_regeneration:
            import_file_contents(test_init_file)

    except Exception as error:  # noqa: PLW0703
        logger.error("Failed to create test import for %s = %s", test_class_name, error)


# ==============================================================================================================
def generate_test_imports(test_directory: Path, write_file: bool = False) -> List[str]:
    """Generates a set of imports for the ogn test modules contained in a directory.

    Args:
        test_directory: Directory containing all of the ogn test scripts. No subdirectories are checked.
        write_file: If True then write the __init__.py file into the named directory, otherwise just return the contents

    Returns:
        List of lines of Python code that comprise the test import code required in the __init__.py file
    """
    # Get the statements to perform safe import of the test files
    test_init_file = test_directory / "__init__.py" if write_file else None
    file_contents = import_file_contents(test_init_file)

    return file_contents
