# Copyright 3D Control Systems, Inc. All Rights Reserved 2017-2019.
# Built in San Francisco.

# This software is distributed under a commercial license for personal,
# educational, corporate or any other use.
# The software as a whole or any parts of it is prohibited for distribution or
# use without obtaining a license from 3D Control Systems, Inc.

# All software licenses are subject to the 3DPrinterOS terms of use
# (available at https://www.3dprinteros.com/terms-and-conditions/),
# and privacy policy (available at https://www.3dprinteros.com/privacy-policy/)

import os
import sys
import logging
import subprocess

import config
import paths
import platforms
from awaitable import Awaitable


private_key_path = "/tmp/3dprinteros_key"
public_key_path = private_key_path + ".pub"
ssh_authorized_keys_path = "/root/.ssh/authorized_keys"
default_shadow_file_path = "/etc/shadow"


BROKEN_OS_TRUST = True


def is_admin():
    try:
        is_admin = os.getuid() == 0
    except:
        import ctypes
        is_admin = ctypes.windll.shell32.IsUserAnAdmin()
    return is_admin


def check_ssh_dir():
    dirname = os.path.dirname(ssh_authorized_keys_path)
    if not os.path.exists(dirname):
        os.mkdir(dirname)


def revoke_all_ssh_keys():
    logger = logging.getLogger(__name__)
    logger.info('Removing ssh authorized keys')
    try:
        check_ssh_dir()
        with open(ssh_authorized_keys_path, 'w') as f:
            f.write('')
    except Exception as e:
        logger.warning("Error while erasing authorized keys!\n%s" % e)
    else:
        restart_sshd()
        return True


def restart_sshd():
    logger = logging.getLogger(__name__)
    sshd_restart_command = ['systemctl', 'restart', 'sshd.service']
    logger.info('Restarting sshd...')
    if subprocess.run(sshd_restart_command).returncode:
        logger.error('sshd restart failed')
    else:
        logger.info('sshd successfully restarted')


def add_public_key(public_key):
    logger = logging.getLogger(__name__)
    try:
        check_ssh_dir()
        with open(ssh_authorized_keys_path, "r") as f:
            ssh_authorized_keys = f.read()
        if not public_key in ssh_authorized_keys:
            with open(ssh_authorized_keys_path, "a") as f:
                f.write(public_key)
            logger.info('Public key is added to authorized keys')
            restart_sshd()
    except Exception as e:
        logger.warning('Cant write public key into authorized_keys:\n%s' % e)
        return True


def generate_ssh_keys():
    logger = logging.getLogger(__name__)
    try:
        os.remove(public_key_path)
    except:
        pass
    try:
        os.remove(private_key_path)
    except:
        pass
    try:
        key_generation_line = "ssh-keygen", "-t", "rsa" "-N", "''", "-f", private_key_path
        logger.info('Launching key generation subprocess...')
        if subprocess.run(key_generation_line).returncode:
            logger.error("Can't generate ssh keys. Non zero return code.")
            return
        logger.info('Keys are generated')
        with open(public_key_path) as f:
            public_key = f.read()
        os.remove(public_key_path)
    except Exception as e:
        logger.error('Error during key generation subprocess\n%s' % e)
    else:
        add_public_key(public_key)
        return private_key_path


def change_ssh_password(password):
    if platforms.get_platform() == 'rpi':
        from patch_shadow import change_password
        try:
            change_password(default_shadow_file_path, password)
            restart_sshd()
            return True
        except:
            pass
    else:
        logger = logging.getLogger(__name__)
        logger.warning("Warning! System password change attempt on non RPi platform!")


def add_cacert_to_certifi(cert_path=paths.CUSTOM_CACERT_PATH):
    try:
        with open(paths.CERTIFI_CACERT_PATH) as f:
            old_cacerts = f.read()
        with open(paths.CERTIFI_CACERT_PATH, 'a') as f_out:
            with open(cert_path) as f_in:
                new_cacert = f_in.read()
                if not new_cacert in old_cacerts:
                    f_out.write('\n')
                    f_out.write(new_cacert)
        subprocess.run(['sync'])
    except (OSError, IOError):
        return "Error on update of certificates in certifi package"


def add_cacert_to_trust():
    if not config.get_app().is_sudo_available():
        return "Error. No access to sudo"
    else:
        try:
            if BROKEN_OS_TRUST:
                subprocess.run(['sudo', 'trust', 'anchor', '/etc/ca-certificates/extracted/tls-ca-bundle.pem'])
            subprocess.run(['sudo', 'trust', 'anchor', paths.CUSTOM_CACERT_PATH])
            subprocess.run(['sudo', 'trust', 'extract-compat'])
            #subprocess.run(['sudo', 'update-ca-trust'])
            subprocess.run(['sync'])
        except (subprocess.CalledProcessError, OSError):
            return "Error on update of system certificates"


class RightsChecker(Awaitable):

    NAME = 'Linux groups membership'
    # pylint: disable=method-hidden

    def check_function(self):
        if sys.platform.startswith('linux') and config.get_settings()['linux_rights_warning'] and not is_admin():
            self.logger.debug('Checking Linux rights')
            result = str(self.execute_command('groups'))
            if not ('tty' in result and 'dialout' in result and 'usbusers' in result):
                self.logger.debug('Current Linux user is not in tty and dialout groups')
                return False
        return True

    def add_user_groups(self):
        if sys.platform.startswith('linux'):
            self.logger.debug('Adding Linux user to necessary groups')
            self.execute_command(['groupadd', 'usbusers'])
            self.execute_command('gksu "sudo usermod -a -G dialout,tty,usbusers $USER"', shell=True)

    def execute_command(self, command, shell=False):
        self.logger.debug('Executing command: ' + str(command))
        try:
            stdout = subprocess.run(command, stdout=subprocess.PIPE, universal_newlines=True).stdout
        except Exception as e:
            self.logger.debug('Error while executing command: "' + str(command) + '\n' + str(e))
        else:
            if stdout:
                self.logger.debug('Execution result: ' + str(stdout))
                return stdout
