"""
Python PIP Workflow
"""

import logging

from aws_lambda_builders.actions import CleanUpAction, CopySourceAction, LinkSourceAction
from aws_lambda_builders.path_resolver import PathResolver
from aws_lambda_builders.workflow import BaseWorkflow, BuildDirectory, BuildInSourceSupport, Capability
from aws_lambda_builders.workflows.python_pip.validator import PythonRuntimeValidator

from .actions import PythonPipBuildAction
from .utils import OSUtils, is_experimental_build_improvements_enabled

LOG = logging.getLogger(__name__)


class PythonPipWorkflow(BaseWorkflow):
    NAME = "PythonPipBuilder"

    CAPABILITY = Capability(language="python", dependency_manager="pip", application_framework=None)

    # Common source files to exclude from build artifacts output
    # Trimmed version of https://github.com/github/gitignore/blob/master/Python.gitignore
    EXCLUDED_FILES = (
        ".aws-sam",
        ".chalice",
        ".git",
        ".gitignore",
        # Compiled files
        "*.pyc",
        "__pycache__",
        "*.so",
        # Distribution / packaging
        ".Python",
        "*.egg-info",
        "*.egg",
        # Installer logs
        "pip-log.txt",
        "pip-delete-this-directory.txt",
        # Unit test / coverage reports
        "htmlcov",
        ".tox",
        ".nox",
        ".coverage",
        ".cache",
        ".pytest_cache",
        # pyenv
        ".python-version",
        # mypy, Pyre
        ".mypy_cache",
        ".dmypy.json",
        ".pyre",
        # environments
        ".env",
        ".venv",
        "venv",
        "venv.bak",
        "env.bak",
        "ENV",
        "env",
        # Editors
        # TODO: Move the commonly ignored files to base class
        ".vscode",
        ".idea",
    )

    PYTHON_VERSION_THREE = "3"

    DEFAULT_BUILD_DIR = BuildDirectory.SCRATCH
    BUILD_IN_SOURCE_SUPPORT = BuildInSourceSupport.NOT_SUPPORTED

    def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtime=None, osutils=None, **kwargs):
        super(PythonPipWorkflow, self).__init__(
            source_dir, artifacts_dir, scratch_dir, manifest_path, runtime=runtime, **kwargs
        )

        if osutils is None:
            osutils = OSUtils()

        if not self.download_dependencies and not self.dependencies_dir:
            LOG.info(
                "download_dependencies is False and dependencies_dir is None. Copying the source files into the "
                "artifacts directory. "
            )

        self.actions = []
        if not osutils.file_exists(manifest_path):
            LOG.warning("requirements.txt file not found. Continuing the build without dependencies.")
            self.actions.append(CopySourceAction(source_dir, artifacts_dir, excludes=self.EXCLUDED_FILES))
            return

        # If a requirements.txt exists, run pip builder before copy action.
        if self.download_dependencies:
            if self.dependencies_dir:
                # clean up the dependencies folder before installing
                self.actions.append(CleanUpAction(self.dependencies_dir))
            self.actions.append(
                PythonPipBuildAction(
                    artifacts_dir,
                    scratch_dir,
                    manifest_path,
                    runtime,
                    self.dependencies_dir,
                    binaries=self.binaries,
                    architecture=self.architecture,
                )
            )
        # if dependencies folder is provided, copy dependencies from dependencies folder to build folder
        # if combine_dependencies is false, will not copy the dependencies from dependencies folder to artifact
        # folder
        if self.dependencies_dir and self.combine_dependencies:
            # when copying downloaded dependencies back to artifacts folder, don't exclude anything
            # symlinking python dependencies is disabled for now since it is breaking sam local commands
            if False and is_experimental_build_improvements_enabled(self.experimental_flags):
                self.actions.append(LinkSourceAction(self.dependencies_dir, artifacts_dir))
            else:
                self.actions.append(CopySourceAction(self.dependencies_dir, artifacts_dir))

        self.actions.append(CopySourceAction(source_dir, artifacts_dir, excludes=self.EXCLUDED_FILES))

    def get_resolvers(self):
        """
        Specialized Python path resolver that looks for additional binaries in addition to the language specific binary.
        """
        return [
            PathResolver(
                runtime=self.runtime,
                binary=self.CAPABILITY.language,
                additional_binaries=self._get_additional_binaries(),
                executable_search_paths=self.executable_search_paths,
            )
        ]

    def _get_additional_binaries(self):
        # python3 is an additional binary that has to be considered in addition to the original python binary, when
        # the specified python runtime is 3.x
        major, _ = self.runtime.replace(self.CAPABILITY.language, "").split(".")
        return [f"{self.CAPABILITY.language}{major}"] if major == self.PYTHON_VERSION_THREE else None

    def get_validators(self):
        return [PythonRuntimeValidator(runtime=self.runtime, architecture=self.architecture)]
