# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

"""Metadata parsing."""

from __future__ import absolute_import
from __future__ import print_function

from collections import namedtuple
import json
import sys

_metadata_lib = None

METADATA_LIB_STDLIB = 'importlib.metadata'
METADATA_LIB_BACKPORT = 'importlib_metadata'
METADATA_LIB_LEGACY = 'pkg_resources'

entrypoint = namedtuple('entrypoint', ['module_name', 'attrs'])
dist = namedtuple('dist', ['egg_base', 'egg_info', 'egg_name', 'egg_version'])


def _get_metadata_lib():
    """Retrieve the correct metadata library to use."""
    global _metadata_lib

    if _metadata_lib is not None:
        return _metadata_lib

    # try importlib.metadata first. This will be available from the stdlib
    # starting in python >= 3.8
    if sys.version_info >= (3, 8):
        _metadata_lib = METADATA_LIB_STDLIB
        return _metadata_lib

    # try importlib_metadata next. This must be installed from PyPI and we
    # don't vendor it, but if available it will be preferred since later
    # versions of pkg_resources issue very annoying deprecation warnings
    try:
        import importlib_metadata  # noqa

        _metadata_lib = METADATA_LIB_BACKPORT
        return _metadata_lib
    except ImportError:
        pass

    # pkg_resources is our fallback. This will always be available on older
    # Python versions since it's part of setuptools.
    try:
        import pkg_resources  # noqa

        _metadata_lib = METADATA_LIB_LEGACY
        return _metadata_lib
    except ImportError:
        pass

    raise RuntimeError(
        'Failed to find a library for loading metadata. This should not '
        'happen. Please report a bug against pbr.'
    )


def get_distributions():
    metadata_lib = _get_metadata_lib()
    if metadata_lib == METADATA_LIB_STDLIB:
        import importlib.metadata

        data = sorted(
            importlib.metadata.distributions(),
            key=lambda x: x.metadata['name'].lower(),
        )
    elif metadata_lib == METADATA_LIB_BACKPORT:
        import importlib_metadata

        data = sorted(
            importlib_metadata.distributions(),
            key=lambda x: x.metadata['name'].lower(),
        )
    else:  # METADATA_LIB_LEGACY
        import pkg_resources

        data = sorted(
            pkg_resources.working_set,
            key=lambda dist: dist.project_name.lower(),
        )

    return list(data)


class PackageNotFound(Exception):
    def __init__(self, package_name):
        self.package_name = package_name

    def __str__(self):
        return 'Package {0} not installed'.format(self.package_name)


def get_metadata(package_name):
    metadata_lib = _get_metadata_lib()
    if metadata_lib == METADATA_LIB_STDLIB:
        import importlib.metadata

        try:
            data = importlib.metadata.distribution(package_name).metadata[
                'pbr.json'
            ]
        except importlib.metadata.PackageNotFoundError:
            raise PackageNotFound(package_name)
    elif metadata_lib == METADATA_LIB_BACKPORT:
        import importlib_metadata

        try:
            data = importlib_metadata.distribution(package_name).metadata[
                'pbr.json'
            ]
        except importlib_metadata.PackageNotFoundError:
            raise PackageNotFound(package_name)
    else:  # METADATA_LIB_LEGACY
        import pkg_resources

        try:
            data = pkg_resources.get_distribution(package_name).get_metadata(
                'pbr.json'
            )
        except pkg_resources.DistributionNotFound:
            raise PackageNotFound(package_name)

    try:
        return json.loads(data)
    except Exception:
        # TODO(stephenfin): We should log an error here. Can we still use
        # distutils.log in the future?
        return None


def get_version(package_name):
    metadata_lib = _get_metadata_lib()
    if metadata_lib == METADATA_LIB_STDLIB:
        import importlib.metadata

        try:
            return importlib.metadata.distribution(package_name).version
        except importlib.metadata.PackageNotFoundError:
            raise PackageNotFound(package_name)
    elif metadata_lib == METADATA_LIB_BACKPORT:
        import importlib_metadata

        try:
            return importlib_metadata.distribution(package_name).version
        except importlib_metadata.PackageNotFoundError:
            raise PackageNotFound(package_name)
    else:  # METADATA_LIB_LEGACY
        import pkg_resources

        try:
            return pkg_resources.get_distribution(package_name).version
        except pkg_resources.DistributionNotFound:
            raise PackageNotFound(package_name)


def get_entry_points(dist, group):
    metadata_lib = _get_metadata_lib()

    if metadata_lib == METADATA_LIB_STDLIB:
        import importlib.metadata

        try:
            dist = importlib.metadata.Distribution.at(dist.egg_info)
        except importlib.metadata.PackageNotFoundError:
            raise PackageNotFound(dist.egg_name)

        # the stdlib library (!!!) changed its behavior in Python 3.10 :(
        # https://docs.python.org/3.10/library/importlib.metadata.html#entry-points
        if hasattr(importlib.metadata, 'EntryPoints'):
            x = [
                (
                    ep.name,
                    entrypoint(
                        module_name=ep.module,
                        attrs=ep.attr.split('.'),
                    ),
                )
                for ep in dist.entry_points.select(group=group)
            ]
            return x
        else:
            x = [
                (
                    ep.name,
                    entrypoint(
                        module_name=ep.value.split(':')[0],
                        attrs=ep.value.split(':')[1].split('.'),
                    ),
                )
                for ep in dist.entry_points
                if ep.group == group
            ]
            return x
    elif metadata_lib == METADATA_LIB_BACKPORT:
        import importlib_metadata

        try:
            dist = importlib_metadata.Distribution.at(dist.egg_info)
        except importlib_metadata.PackageNotFoundError:
            raise PackageNotFound(dist.egg_name)

        # as above
        if hasattr(importlib_metadata, 'EntryPoints'):
            x = [
                (
                    ep.name,
                    entrypoint(
                        module_name=ep.module,
                        attrs=ep.attr.split('.'),
                    ),
                )
                for ep in dist.entry_points.select(group=group)
            ]
            return x
        else:
            x = [
                (
                    ep.name,
                    entrypoint(
                        module_name=ep.value.split(':')[0],
                        attrs=ep.value.split(':')[1].split('.'),
                    ),
                )
                for ep in dist.entry_points
                if ep.group == group
            ]
            return x
    else:  # METADATA_LIB_LEGACY
        import pkg_resources

        try:
            dist = pkg_resources.Distribution(
                dist.egg_base,
                pkg_resources.PathMetadata(dist.egg_base, dist.egg_info),
                dist.egg_name,
                dist.egg_version,
            )
        except pkg_resources.DistributionNotFound:
            raise PackageNotFound(dist.egg_name)

        return [
            (name, entrypoint(module_name=ep.module_name, attrs=ep.attrs))
            for name, ep in dist.get_entry_map(group).items()
        ]
