#
# Copyright (c) 2014 Red Hat, Inc.
#
# 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 logging
import re
import os
import subprocess

from subscription_manager.plugin.ostree import config

OSTREE_REPO_CONFIG_PATH = "/etc/ostree/remotes.d/redhat.conf"
OSTREE_CORE_CONFIG_PATH = "/ostree/repo/config"

REMOTE_SECTION_MATCH = r"remote
\s+\"(?P<remote_name>.+)\""

OSTREE_REPORT_TEMPLATE = """remote \"{self.name}\"
\turl: {self.url}
\tgpg-verify: {self.gpg_verify}
\ttls-client-cert-path: {self.tls_client_cert_path}
\ttls-client-key-
path: {self.tls_client_key_path}
\ttls-ca-path: {self.tls_ca_path}"""

log = logging.getLogger("rhsm-app." + __name__)


class OstreeContentError(Exception):
    pass


class RemoteSectionNameParseError(OstreeContentError):
    def __init__(self, msg=None, section=None):
        self.msg = msg
        self.section = section


class OstreeRemote(object):
    """Represent a ostree repo remote.

    A repo remote is one of the the '[remote "ostree-awesomeos-8"]' section in
    ostree repo config (/ostree/repo/config by default).
    """

    items_to_data = {'gpg-verify': 'gpg_verify',
                     'tls-client-cert-path': 'tls_client_cert_path',
                     'tls-client-key-path': 'tls_client_key_path',
                     'tls-ca-path': 'tls_ca_path'}

    report_template = OSTREE_REPORT_TEMPLATE

    def __init__(self):
        self.data = {}

    # for remote_key in remote iterates over the config items
    def __iter__(self):
        return iter(self.data)

    @property
    def url(self):
        return self.data.get('url')

    @url.setter
    def url(self, value):
        self.data['url'] = value

    @property
    def gpg_verify(self):
        return self.data.get('gpg_verify', True)

    @gpg_verify.setter
    def gpg_verify(self, value):
        self.data['gpg_verify'] = value

    @property
    def name(self):
        return self.data.get('name')

    @name.setter
    def name(self, value):
        self.data['name'] = value

    @property
    def tls_client_cert_path(self):
        return self.data.get('tls_client_cert_path')

    @tls_client_cert_path.setter
    def tls_client_cert_path(self, value):
        self.data['tls_client_cert_path'] = value

    @property
    def tls_client_key_path(self):
        return self.data.get('tls_client_key_path')

    @tls_client_key_path.setter
    def tls_client_key_path(self, value):
        self.data['tls_client_key_path'] = value

    @property
    def tls_ca_path(self):
        return self.data.get('tls_ca_path')

    @tls_ca_path.setter
    def tls_ca_path(self, value):
        self.data['tls_ca_path'] = value

    @classmethod
    def from_config_section(cls, section, items):
        """Create a OstreeRemote object from a repo config section name and map of items.

        'section' is the name of the remote section in the repo config file. For
          ex: 'remote "ostree-awesomeos-8"'
        'items' is a map of items corresponding to config items for 'section'. Extra
          items we don't understand are ignored. Expect at least 'url'.

        Note: 'gpg-verify' is one of the default items, but 'gpg-verify' is not
              a valid python attribute name, so a key of 'gpg-verify' is used to
              update the OstreeRemote.gpg_verify property.
        """
        remote = cls()

        # transmogrify names
        for key in items:
            # replace key name with mapping name, defaulting to key name
            remote.data[cls.items_to_data.get(key, key)] = items[key]

        # the section name takes precendence over a 'name' in the items
        remote.name = OstreeRemote.name_from_section(section)
        return remote

    @staticmethod
    def name_from_section(section):
        """Parse the remote name from the name of the config file section.

        ie, 'remote "awesome-os-7-ostree"' -> "awesome-os-7-ostree".
        """
        matcher = re.compile(REMOTE_SECTION_MATCH)
        result = matcher.match(section)
        if result:
            return result.groupdict()['remote_name']

        raise RemoteSectionNameParseError("Unable to find a name in section %s" % section,
                                          section=section)

    @classmethod
    def from_ent_cert_content(cls, content):
        """Create a OstreeRemote object based on a model.Content object.

        This maps:
            Content.label -> OstreeRemote.name
            Content.url -> OstreeRemote.url

        OstreeRemote.branches is always None for now.
        """

        remote = cls()
        remote.name = content.label
        remote.url = content.url

        remote.gpg_verify = remote.map_gpg(content)

        cert = content.cert
        remote.tls_client_cert_path = cert.path
        remote.tls_client_key_path = cert.key_path()

        # NOTE: The tls-ca-path info is not in the Content,
        #       but local system configuration.

        return remote

    @staticmethod
    def map_gpg(content):
        """Map the content.gpg attribute value to True/False/

        Content.gpg is meant for a URL, and is being overloaded
        here for enabled/disabled. Content's representing content
        that is not associated with a gpg key have their 'gpg'
        attribute ('gpg_url' in the json in an ent cert)
        set to "http://". So consider that value also equilivent
        to gpg-verify=false. That is the only value that will
        disable gpg-verify.

        If there are any errors reading/understand the
        Content.gpg, assume gpg_verify = True
        """
        gpg_verify = True

        # No content.gpg means gpg-verify=true as well.
        # This reinforces "no explicit Content.gpg"
        # means to default to gpg_verify=True
        if not hasattr(content, 'gpg'):
            return gpg_verify

        if content.gpg == "http://":
            gpg_verify = False

        # If we need some other sentinal to indicate gpg-verify=false,
        # look for it here.

        return gpg_verify

    def __repr__(self):
        r = super(OstreeRemote, self).__repr__()
        template = "%s\n (name=%s\n url=%s\n gpg_verify=%s\n tls_client_cert_path=%s\n tls_client_key_path=%s)"
        return template % (r, self.name, self.url, self.gpg_verify,
               self.tls_client_cert_path, self.tls_client_key_path)

    def report(self):
        return self.report_template.format(self=self)


class OstreeRemotes(object):
    """A container/set of OstreeRemote objects.

    Representing OstreeRemote's as found in the repo configs, or
    as created from ent cert Content objects.
    """
    def __init__(self):
        self.data = []

    def add(self, ostree_remote):
        self.data.append(ostree_remote)

    # we can iterate over OstreeRemotes
    def __iter__(self):
        return iter(self.data)

    def __len__(self):
        return len(self.data)

    def __getitem__(self, key):
        return self.data[key]

    @classmethod
    def from_config(cls, repo_config):
        """Create a OstreeRemotes from a repo_file.RepoFile object."""
        remotes = cls()
        sections = repo_config.remote_sections()
        for section in sections:
            item_list = repo_config.config_parser.items(section)
            items = dict(item_list)
            remote = OstreeRemote.from_config_section(section, items)
            remotes.add(remote)
        return remotes

    def __str__(self):
        s = "\n%s\n" % self.__class__
        for remote in self.data:
            s = s + " %s\n" % repr(remote)
        s = s + "</OstreeRemotes>\n"
        return s


class OstreeConfigFileStore(object):
    """