from __future__ import print_function, division, absolute_import

#
# Copyright (c) 2010 Red Hat, Inc.
#
# Authors: Jeff Ortel <jortel@redhat.com>
#
# This software is licensed to you under the GNU General Public License,
# version 2 (GPLv2). There is NO WARRANTY for this software, express or
# implied, including the implied warranties of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
# along with this software; if not, see
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
#
# Red Hat trademarks are not licensed under GPLv2. No permission is
# granted to use or replicate Red Hat trademarks that are incorporated
# in this software or its documentation.
#

import os
from yum.plugins import TYPE_CORE

from subscription_manager import injection as inj
from subscription_manager.action_client import ProfileActionClient
from subscription_manager.repolib import RepoActionInvoker
from subscription_manager.entcertlib import EntCertActionInvoker
from rhsmlib.facts.hwprobe import ClassicCheck
from subscription_manager.certlib import Locker
from subscription_manager.utils import chroot, is_simple_content_access
from subscription_manager.injectioninit import init_dep_injection
from subscription_manager import logutil
from rhsm import config

requires_api_version = '2.5'
plugin_type = (TYPE_CORE,)

# TODO: translate strings

expired_warning = """
*** WARNING ***
The subscription for following product(s) has expired:
%s
You no longer have access to the repositories that provide these products.  It is important that you apply an active \
subscription in order to resume access to security and other critical updates. If you don't have other active \
subscriptions, you can renew the expired subscription.
"""

not_registered_warning = """
This system is not registered with an entitlement server. You can use subscription-manager to register.
"""

no_subs_warning = """
This system is registered with an entitlement server, but is not receiving updates. You can use subscription-manager \
to assign subscriptions.
"""

no_subs_container_warning = """
This system is not receiving updates. You can use subscription-manager on the host to register and assign subscriptions.
"""


# If running from the yum plugin, we want to avoid blocking
# yum forever on locks created by other rhsm processes. We try
# to acquire the lock before potentially updating redhat.repo, but
# if we can't, the plugin will fail back to not updating it.
# So this class provides a Locker uses the default ACTION_LOCK,
# but if it would have to wait for it, it decides to do nothing
# instead.
class YumRepoLocker(Locker):
    def __init__(self, conduit):
        super(YumRepoLocker, self).__init__()
        self.conduit = conduit

    def run(self, action):
        # lock.acquire will return False if it would block
        # NOTE: acquire can return None, True, or False
        #       with different meanings.
        non_blocking = self.lock.acquire(blocking=False)
        if non_blocking is False:
            # Could try to grab the pid for the log message, but it's a bit of a race.
            self.conduit.info(3, "Another process has the cert lock. We will not attempt to update certs or repos.")
            return 0
        try:
            return action()
        finally:
            self.lock.release()


def update(conduit, cache_only):
    """
    Update entitlement certificates
    """
    if os.getuid() != 0:
        conduit.info(3, 'Not root, Subscription Management repositories not updated')
        return
    conduit.info(3, 'Updating Subscription Management repositories.')

    identity = inj.require(inj.IDENTITY)

    if not identity.is_valid():
        conduit.info(3, "Unable to read consumer identity")

    # In containers we have no identity, but we may have entitlements inherited
    # from the host, which need to generate a redhat.repo.
    if config.in_container():
        conduit.info(3, "Subscription Manager is operating in container mode.")

    if not cache_only and not config.in_container():
        cert_action_invoker = EntCertActionInvoker(locker=YumRepoLocker(conduit=conduit))
        cert_action_invoker.update()

    repo_action_invoker = RepoActionInvoker(cache_only=cache_only, locker=YumRepoLocker(conduit=conduit))
    repo_action_invoker.update()


def warn_expired_entitlements(conduit):
    """
    When some entitlement is expired, then display warning message about it
    """
    ent_dir = inj.require(inj.ENT_DIR)
    products = set()

    for cert in ent_dir.list_expired():
        for product in cert.products:
            m = '  - %s' % product.name
            products.add(m)

    if products:
        msg = expired_warning % '\n'.join(sorted(products))
        conduit.info(2, msg)


def warn_or_usage_message(conduit):
    """
    Display warning message, when the system is not registered (no consumer cert) or then is no entitlement cert
    """

    if os.getuid() != 0:
        return
    if ClassicCheck().is_registered_with_classic():
        return

    msg = ""
    try:
        identity = inj.require(inj.IDENTITY)
        ent_dir = inj.require(inj.ENT_DIR)
        # Don't warn people to register if we see entitlements, but no identity:
        if not identity.is_valid() and len(ent_dir.list_valid()) == 0:
            msg = not_registered_warning
        elif len(ent_dir.list_valid()) == 0 and not is_simple_content_access(identity=identity):
            msg = no_subs_warning
        if config.in_container() and len(ent_dir.list_valid()) == 0:
            msg = no_subs_container_warning
    finally:
        if msg:
            conduit.info(2, msg)


def init_hook(conduit):
    """
    Hook for disabling system repositories (repositories which are
    not mangaged by subscription-manager will NOT be used)
    """

    disable_system_repos = conduit.confBool('main', 'disable_system_repos', default=False)

    if disable_system_repos:
        disable_count = 0
        repo_storage = conduit.getRepos()
        for repo in repo_storage.repos.values():
            if os.path.basename(repo.repofile) != "redhat.repo" and repo.enabled is True:
                conduit.info(2, 'Disabling system repository "%s" in file "%s"' % (repo.id, repo.repofile))
                repo_storage.disableRepo(repo.id)
                disable_count += 1
        conduit.info(2, 'subscription-manager plugin disabled "%d" system repositories with respect of configuration in /etc/yum/pluginconf.d/subscription-manager.conf' % (disable_count))


def config_hook(conduit):
    """
    This is the first hook of this yum plugin that is triggered by yum. So we do initialization
    of all stuff that is necessary by other hooks
    :param conduit: Reference on conduit object used by yum plugin API
    :return: None
    """
    logutil.init_logger_for_yum()

    init_dep_injection()

    if hasattr(conduit, 'registerPackageName'):
        conduit.registerPackageName("subscription-manager")


def postconfig_hook(conduit):
    """
    Try to display some warning messages, when it is necessary.
    :param conduit: Reference on conduit object used by yum plugin API
    :return: None
    """

    # If a tool (it's, e.g., Mock) manages a chroot via 'yum --installroot',
    # we must update entitlements in that directory.
    # Note: conduit.getConf() is available in postconfig_hook
    chroot(conduit.getConf().installroot)

    cfg = config.initConfig()
    cache_only = not bool(cfg.get_int('rhsm', 'full_refresh_on_yum'))

    if hasattr(conduit, 'registerPackageName'):
        conduit.registerPackageName("subscription-manager")

    # It is save to display following warning messages for all yum commands, because following functions
    # does not communicate with candlepin server. See: https://bugzilla.redhat.com/show_bug.cgi?id=1621275
    try:
        update(conduit, cache_only)
        warn_or_usage_message(conduit)
        warn_expired_entitlements(conduit)
    except Exception as e:
        conduit.error(2, str(e))


def posttrans_hook(conduit):
    """
    Upload package profile after transactions if run as a yum plugin (BZ 1742208)
    :param conduit:
    :return: None
    """
    cfg = config.initConfig()
    if '1' == cfg.get('rhsm', 'package_profile_on_trans'):
        conduit.info(3, "Updating package profile")
        package_profile_client = ProfileActionClient()
        package_profile_client.update()
    else:
        # do nothing
        return
