# -*- coding: utf-8 -*-
import re
import shutil
import tempfile

from collections import defaultdict
from contextlib import contextmanager
from typing import Set
from typing import Union

from poetry.utils._compat import Path
from poetry.utils._compat import basestring
from poetry.utils._compat import lru_cache
from poetry.vcs import get_vcs

from ..metadata import Metadata
from ..utils.module import Module
from ..utils.package_include import PackageInclude


AUTHOR_REGEX = re.compile(r"(?u)^(?P<name>[- .,\w\d'’\"()]+) <(?P<email>.+?)>$")


class Builder(object):

    AVAILABLE_PYTHONS = {"2", "2.7", "3", "3.4", "3.5", "3.6", "3.7"}

    def __init__(self, poetry, env, io):
        self._poetry = poetry
        self._env = env
        self._io = io
        self._package = poetry.package
        self._path = poetry.file.parent
        self._module = Module(
            self._package.name,
            self._path.as_posix(),
            packages=self._package.packages,
            includes=self._package.include,
        )
        self._meta = Metadata.from_package(self._package)

    def build(self):
        raise NotImplementedError()

    @lru_cache(maxsize=None)
    def find_excluded_files(self):  # type: () -> Set[str]
        # Checking VCS
        vcs = get_vcs(self._path)
        if not vcs:
            vcs_ignored_files = set()
        else:
            vcs_ignored_files = set(vcs.get_ignored_files())

        explicitely_excluded = set()
        for excluded_glob in self._package.exclude:
            for excluded in self._path.glob(str(excluded_glob)):
                explicitely_excluded.add(excluded.relative_to(self._path).as_posix())

        ignored = vcs_ignored_files | explicitely_excluded
        result = set()
        for file in ignored:
            result.add(file)

        # The list of excluded files might be big and we will do a lot
        # containment check (x in excluded).
        # Returning a set make those tests much much faster.
        return result

    def is_excluded(self, filepath):  # type: (Union[str, Path]) -> bool
        if not isinstance(filepath, basestring):
            filepath = filepath.as_posix()

        return filepath in self.find_excluded_files()

    def find_files_to_add(self, exclude_build=True):  # type: (bool) -> list
        """
        Finds all files to add to the tarball
        """
        to_add = []

        for include in self._module.includes:
            for file in include.elements:
                if "__pycache__" in str(file):
                    continue

                if file.is_dir():
                    continue

                file = file.relative_to(self._path)

                if self.is_excluded(file) and isinstance(include, PackageInclude):
                    continue

                if file.suffix == ".pyc":
                    continue

                if file in to_add:
                    # Skip duplicates
                    continue

                self._io.writeln(
                    " - Adding: <comment>{}</comment>".format(str(file)),
                    verbosity=self._io.VERBOSITY_VERY_VERBOSE,
                )
                to_add.append(file)

        # Include project files
        self._io.writeln(
            " - Adding: <comment>pyproject.toml</comment>",
            verbosity=self._io.VERBOSITY_VERY_VERBOSE,
        )
        to_add.append(Path("pyproject.toml"))

        # If a license file exists, add it
        for license_file in self._path.glob("LICENSE*"):
            self._io.writeln(
                " - Adding: <comment>{}</comment>".format(
                    license_file.relative_to(self._path)
                ),
                verbosity=self._io.VERBOSITY_VERY_VERBOSE,
            )
            to_add.append(license_file.relative_to(self._path))

        # If a README is specificed we need to include it
        # to avoid errors
        if "readme" in self._poetry.local_config:
            readme = self._path / self._poetry.local_config["readme"]
            if readme.exists():
                self._io.writeln(
                    " - Adding: <comment>{}</comment>".format(
                        readme.relative_to(self._path)
                    ),
                    verbosity=self._io.VERBOSITY_VERY_VERBOSE,
                )
                to_add.append(readme.relative_to(self._path))

        # If a build script is specified and explicitely required
        # we add it to the list of files
        if self._package.build and not exclude_build:
            to_add.append(Path(self._package.build))

        return sorted(to_add)

    def convert_entry_points(self):  # type: () -> dict
        result = defaultdict(list)

        # Scripts -> Entry points
        for name, ep in self._poetry.local_config.get("scripts", {}).items():
            extras = ""
            if isinstance(ep, dict):
                extras = "[{}]".format(", ".join(ep["extras"]))
                ep = ep["callable"]

            result["console_scripts"].append("{} = {}{}".format(name, ep, extras))

        # Plugins -> entry points
        plugins = self._poetry.local_config.get("plugins", {})
        for groupname, group in plugins.items():
            for name, ep in sorted(group.items()):
                result[groupname].append("{} = {}".format(name, ep))

        for groupname in result:
            result[groupname] = sorted(result[groupname])

        return dict(result)

    @classmethod
    def convert_author(cls, author):  # type: () -> dict
        m = AUTHOR_REGEX.match(author)

        name = m.group("name")
        email = m.group("email")

        return {"name": name, "email": email}

    @classmethod
    @contextmanager
    def temporary_directory(cls, *args, **kwargs):
        try:
            from tempfile import TemporaryDirectory

            with TemporaryDirectory(*args, **kwargs) as name:
                yield name
        except ImportError:
            name = tempfile.mkdtemp(*args, **kwargs)

            yield name

            shutil.rmtree(name)
