#
# Copyright 2017 Check Point Software Technologies
#
# 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.
#
# Requires Python 2.6+ and Openssl 1.0+
#

import base64
import socket
import struct
import time

import azurelinuxagent.common.conf as conf
from azurelinuxagent.common.exception import OSUtilError
from azurelinuxagent.common.future import ustr, bytebuffer, range, int  # pylint: disable=redefined-builtin
import azurelinuxagent.common.logger as logger
from azurelinuxagent.common.osutil.default import DefaultOSUtil
from azurelinuxagent.common.utils.cryptutil import CryptUtil
import azurelinuxagent.common.utils.fileutil as fileutil
import azurelinuxagent.common.utils.shellutil as shellutil
import azurelinuxagent.common.utils.textutil as textutil


class GaiaOSUtil(DefaultOSUtil):

    def __init__(self):  # pylint: disable=W0235
        super(GaiaOSUtil, self).__init__()

    def _run_clish(self, cmd):
        ret = 0
        out = ""
        for i in range(10):  # pylint: disable=W0612
            try:
                final_command = ["/bin/clish", "-s", "-c", "'{0}'".format(cmd)]
                out = shellutil.run_command(final_command, log_error=True)
                ret = 0
                break
            except shellutil.CommandError as e:
                ret = e.returncode
                out = e.stdout
            except Exception as e:
                ret = -1
                out = ustr(e)

            if 'NMSHST0025' in out:  # Entry for [hostname] already present
                ret = 0
                break
            time.sleep(2)
        return ret, out

    def useradd(self, username, expiration=None, comment=None):
        logger.warn('useradd is not supported on GAiA')

    def chpasswd(self, username, password, crypt_id=6, salt_len=10):
        logger.info('chpasswd')
        passwd_hash = textutil.gen_password_hash(password, crypt_id, salt_len)
        ret, out = self._run_clish(
            'set user admin password-hash ' + passwd_hash)
        if ret != 0:
            raise OSUtilError(("Failed to set password for {0}: {1}"
                               "").format('admin', out))

    def conf_sudoer(self, username, nopasswd=False, remove=False):
        logger.info('conf_sudoer is not supported on GAiA')

    def del_root_password(self):
        logger.info('del_root_password')
        ret, out = self._run_clish('set user admin password-hash *LOCK*')  # pylint: disable=W0612
        if ret != 0:
            raise OSUtilError("Failed to delete root password")

    def _replace_user(self, path, username):
        if path.startswith('$HOME'):
            path = '/home' + path[5:]
        parts = path.split('/')
        parts[2] = username
        return '/'.join(parts)

    def deploy_ssh_keypair(self, username, keypair):
        logger.info('deploy_ssh_keypair')
        username = 'admin'
        path, thumbprint = keypair
        path = self._replace_user(path, username)
        super(GaiaOSUtil, self).deploy_ssh_keypair(
            username, (path, thumbprint))

    def openssl_to_openssh(self, input_file, output_file):
        cryptutil = CryptUtil(conf.get_openssl_cmd())
        ret, out = shellutil.run_get_output(
            conf.get_openssl_cmd() +
            " rsa -pubin -noout -text -in '" + input_file + "'")
        if ret != 0:
            raise OSUtilError('openssl failed with {0}'.format(ret))

        modulus = []
        exponent = []
        buf = None
        for line in out.split('\n'):
            if line.startswith('Modulus:'):
                buf = modulus
                buf.append(line)
                continue
            if line.startswith('Exponent:'):
                buf = exponent
                buf.append(line)
                continue
            if buf and line:
                buf.append(line.strip().replace(':', ''))

        def text_to_num(buf):
            if len(buf) == 1:
                return int(buf[0].split()[1])
            return int(''.join(buf[1:]), 16)

        n = text_to_num(modulus)
        e = text_to_num(exponent)

        keydata = bytearray()
        keydata.extend(struct.pack('>I', len('ssh-rsa')))
        keydata.extend(b'ssh-rsa')
        keydata.extend(struct.pack('>I', len(cryptutil.num_to_bytes(e))))
        keydata.extend(cryptutil.num_to_bytes(e))
        keydata.extend(struct.pack('>I', len(cryptutil.num_to_bytes(n)) + 1))
        keydata.extend(b'\0')
        keydata.extend(cryptutil.num_to_bytes(n))
        keydata_base64 = base64.b64encode(bytebuffer(keydata))
        fileutil.write_file(output_file,
                            ustr(b'ssh-rsa ' + keydata_base64 + b'\n',
                                 encoding='utf-8'))

    def deploy_ssh_pubkey(self, username, pubkey):
        logger.info('deploy_ssh_pubkey')
        username = 'admin'
        path, thumbprint, value = pubkey
        path = self._replace_user(path, username)
        super(GaiaOSUtil, self).deploy_ssh_pubkey(
            username, (path, thumbprint, value))

    def eject_dvd(self, chk_err=True):
        logger.warn('eject is not supported on GAiA')

    def mount(self, device, mount_point, option=None, chk_err=True):
        if not option:
            option = []

        if any('udf,iso9660' in opt for opt in option):
            ret, out = super(GaiaOSUtil, self).mount(device, mount_point,
                option=[opt.replace('udf,iso9660', 'udf') for opt in option],
                chk_err=chk_err)
            if not ret:
                return ret, out
        return super(GaiaOSUtil, self).mount(
            device, mount_point, option=option, chk_err=chk_err)

    def allow_dhcp_broadcast(self):
        logger.info('allow_dhcp_broadcast is ignored on GAiA')

    def remove_rules_files(self, rules_files=''):
        pass

    def restore_rules_files(self, rules_files=''):
        logger.info('restore_rules_files is ignored on GAiA')

    def restart_ssh_service(self):
        return shellutil.run('/sbin/service sshd condrestart', chk_err=False)

    def _address_to_string(self, addr):
        return socket.inet_ntoa(struct.pack("!I", addr))

    def _get_prefix(self, mask):
        return str(sum([bin(int(x)).count('1') for x in mask.split('.')]))

    def route_add(self, net, mask, gateway):
        logger.info('route_add {0} {1} {2}', net, mask, gateway)

        if net == 0 and mask == 0:
            cidr = 'default'
        else:
            cidr = self._address_to_string(net) + '/' + self._get_prefix(
                self._address_to_string(mask))

        ret, out = self._run_clish(  # pylint: disable=W0612
            'set static-route ' + cidr +
            ' nexthop gateway address ' +
            self._address_to_string(gateway) + ' on')
        return ret

    def set_hostname(self, hostname):
        logger.warn('set_hostname is ignored on GAiA')

    def set_dhcp_hostname(self, hostname):
        logger.warn('set_dhcp_hostname is ignored on GAiA')

    def publish_hostname(self, hostname):
        logger.warn('publish_hostname is ignored on GAiA')

    def del_account(self, username):
        logger.warn('del_account is ignored on GAiA')
