# -*- coding: utf-8 -*-
#
# Copyright 2016 SUSE LLC
#
# 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.

# Import python libs
from __future__ import absolute_import
import os
from xml.dom import minidom
import platform
import socket
try:
    import grp
    import pwd
except ImportError:
    pass

# Import salt libs
import salt.utils.files
from salt.modules.inspectlib.exceptions import InspectorKiwiProcessorException

# Import third party libs
try:
    from lxml import etree
except ImportError:
    from salt._compat import ElementTree as etree


class KiwiExporter(object):
    '''
    Exports system description as Kiwi configuration.
    '''
    def __init__(self, grains, format):
        self.__grains__ = grains
        self.format = format
        self._data = type('data', (), {})
        self.name = None

    def load(self, **descr):
        '''
        Load data by keys.

        :param data:
        :return:
        '''
        for obj, data in descr.items():
            setattr(self._data, obj, data)

        return self

    def export(self, name):
        '''
        Export to the Kiwi config.xml as text.

        :return:
        '''

        self.name = name
        root = self._create_doc()
        self._set_description(root)
        self._set_preferences(root)
        self._set_repositories(root)
        self._set_users(root)
        self._set_packages(root)

        return '\n'.join([line for line in minidom.parseString(
            etree.tostring(root, encoding='UTF-8', pretty_print=True)).toprettyxml(indent="  ").split("\n")
                          if line.strip()])

    def _get_package_manager(self):
        '''
        Get package manager.

        :return:
        '''
        ret = None
        if self.__grains__.get('os_family') in ('Kali', 'Debian'):
            ret = 'apt-get'
        elif self.__grains__.get('os_family', '') == 'Suse':
            ret = 'zypper'
        elif self.__grains__.get('os_family', '') == 'redhat':
            ret = 'yum'

        if ret is None:
            raise InspectorKiwiProcessorException('Unsupported platform: {0}'.format(self.__grains__.get('os_family')))

        return ret

    def _set_preferences(self, node):
        '''
        Set preferences.

        :return:
        '''
        pref = etree.SubElement(node, 'preferences')
        pacman = etree.SubElement(pref, 'packagemanager')
        pacman.text = self._get_package_manager()
        p_version = etree.SubElement(pref, 'version')
        p_version.text = '0.0.1'
        p_type = etree.SubElement(pref, 'type')
        p_type.set('image', 'vmx')

        for disk_id, disk_data in self._data.system.get('disks', {}).items():
            if disk_id.startswith('/dev'):
                p_type.set('filesystem', disk_data.get('type') or 'ext3')
                break

        p_type.set('installiso', 'true')
        p_type.set('boot', "vmxboot/suse-leap42.1")
        p_type.set('format', self.format)
        p_type.set('bootloader', 'grub2')
        p_type.set('timezone', __salt__['timezone.get_zone']())
        p_type.set('hwclock', __salt__['timezone.get_hwclock']())

        return pref

    def _get_user_groups(self, user):
        '''
        Get user groups.

        :param user:
        :return:
        '''
        return [g.gr_name for g in grp.getgrall()
                if user in g.gr_mem] + [grp.getgrgid(pwd.getpwnam(user).pw_gid).gr_name]

    def _set_users(self, node):
        '''
        Create existing local users.

        <users group="root">
          <user password="$1$wYJUgpM5$RXMMeASDc035eX.NbYWFl0" home="/root" name="root"/>
        </users>

        :param node:
        :return:
        '''
        # Get real local users with the local passwords
        shadow = {}
        with salt.utils.files.fopen('/etc/shadow') as rfh:
            for sh_line in rfh.read().split(os.linesep):
                if sh_line.strip():
                    login, pwd = sh_line.split(":")[:2]
                    if pwd and pwd[0] not in '!*':
                        shadow[login] = {'p': pwd}

        with salt.utils.files.fopen('/etc/passwd') as rfh:
            for ps_line in rfh.read().split(os.linesep):
                if ps_line.strip():
                    ps_line = ps_line.strip().split(':')
                    if ps_line[0] in shadow:
                        shadow[ps_line[0]]['h'] = ps_line[5]
                        shadow[ps_line[0]]['s'] = ps_line[6]
                        shadow[ps_line[0]]['g'] = self._get_user_groups(ps_line[0])

        users_groups = []
        users_node = etree.SubElement(node, 'users')
        for u_name, u_data in shadow.items():
            user_node = etree.SubElement(users_node, 'user')
            user_node.set('password', u_data['p'])
            user_node.set('home', u_data['h'])
            user_node.set('name', u_name)
            users_groups.extend(u_data['g'])
        users_node.set('group', ','.join(users_groups))

        return users_node

    def _set_repositories(self, node):
        '''
        Create repositories.

        :param node:
        :return:
        '''
        priority = 99

        for repo_id, repo_data in self._data.software.get('repositories', {}).items():
            if type(repo_data) == list:
                repo_data = repo_data[0]
            if repo_data.get('enabled') or not repo_data.get('disabled'):  # RPM and Debian, respectively
                uri = repo_data.get('baseurl', repo_data.get('uri'))
                if not uri:
                    continue
                repo = etree.SubElement(node, 'repository')
                if self.__grains__.get('os_family') in ('Kali', 'Debian'):
                    repo.set('alias', repo_id)
                    repo.set('distribution', repo_data['dist'])
                else:
                    repo.set('alias', repo_data['alias'])
                    if self.__grains__.get('os_family', '') == 'Suse':
                        repo.set('type', 'yast2')  # TODO: Check for options!
                    repo.set('priority', str(priority))
                source = etree.SubElement(repo, 'source')
                source.set('path', uri)  # RPM and Debian, respectively
                priority -= 1

    def _set_packages(self, node):
        '''
        Set packages and collections.

        :param node:
        :return:
        '''
        pkgs = etree.SubElement(node, 'packages')
        for pkg_name, pkg_version in sorted(self._data.software.get('packages', {}).items()):
            pkg = etree.SubElement(pkgs, 'package')
            pkg.set('name', pkg_name)

        # Add collections (SUSE)
        if self.__grains__.get('os_family', '') == 'Suse':
            for ptn_id, ptn_data in self._data.software.get('patterns', {}).items():
                if ptn_data.get('installed'):
                    ptn = etree.SubElement(pkgs, 'namedCollection')
                    ptn.set('name', ptn_id)

        return pkgs

    def _set_description(self, node):
        '''
        Create a system description.

        :return:
        '''
        hostname = socket.getfqdn() or platform.node()

        descr = etree.SubElement(node, 'description')
        author = etree.SubElement(descr, 'author')
        author.text = "salt.modules.node on {0}".format(hostname)
        contact = etree.SubElement(descr, 'contact')
        contact.text = 'root@{0}'.format(hostname)
        specs = etree.SubElement(descr, 'specification')
        specs.text = 'Rebuild of {0}, based on Salt inspection.'.format(hostname)

        return descr

    def _create_doc(self):
        '''
        Create document.

        :return:
        '''
        root = etree.Element('image')
        root.set('schemaversion', '6.3')
        root.set('name', self.name)

        return root
