#!/usr/bin/env python
# -*- coding: utf-8 -*-

from os import mkdir, environ, chdir
import platform
from shutil import copy, rmtree, move
import subprocess
import sys

system = platform.system()
working_directory = sys.path[0]

try:
    chdir(working_directory)
    subprocess.call(('pip', 'install', '-r', 'requirements.txt'))
    if system == 'Linux':
        copy("/root/agent/misc/vm_renewal", "/usr/local/bin/")
except:
    pass  # hope it works


import logging
import fileinput
import tarfile
from os.path import expanduser, join, exists
from glob import glob
from StringIO import StringIO
from base64 import decodestring
from hashlib import md5


from ssh import PubKey
from .network import change_ip_ubuntu, change_ip_rhel
from context import BaseContext

from twisted.internet import reactor

logging.basicConfig()
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)

SSH_DIR = expanduser('~cloud/.ssh')
AUTHORIZED_KEYS = join(SSH_DIR, 'authorized_keys')

STORE_DIR = '/store'

mount_template_linux = (
    '//%(host)s/%(username)s %(dir)s cifs username=%(username)s'
    ',password=%(password)s,iocharset=utf8,uid=cloud  0  0\n')


distros = {'Scientific Linux': 'rhel',
           'CentOS': 'rhel',
           'CentOS Linux': 'rhel',
           'Debian': 'debian',
           'Ubuntu': 'debian'}
if system == 'Linux':
    distro = distros[platform.linux_distribution()[0]]


class Context(BaseContext):

    # http://stackoverflow.com/questions/12081310/
    # python-module-to-change-system-date-and-time
    def _linux_set_time(time):
        import ctypes
        import ctypes.util

        CLOCK_REALTIME = 0

        class timespec(ctypes.Structure):
            _fields_ = [("tv_sec", ctypes.c_long),
                        ("tv_nsec", ctypes.c_long)]

        librt = ctypes.CDLL(ctypes.util.find_library("rt"))

        ts = timespec()
        ts.tv_sec = int(time)
        ts.tv_nsec = 0

        librt.clock_settime(CLOCK_REALTIME, ctypes.byref(ts))

    @staticmethod
    def change_password(password):
        proc = subprocess.Popen(['/usr/sbin/chpasswd'],
                                stdin=subprocess.PIPE)
        proc.communicate('cloud:%s\n' % password)

    @staticmethod
    def restart_networking():
        if distro == 'debian':
            subprocess.call(['/etc/init.d/networking', 'restart'])
        elif distro == 'rhel':
            subprocess.call(['/bin/systemctl', 'restart', 'network'])
            pass

    @staticmethod
    def change_ip(interfaces, dns):
        if distro == 'debian':
            change_ip_ubuntu(interfaces, dns)
        elif distro == 'rhel':
            change_ip_rhel(interfaces, dns)

    @staticmethod
    def set_time(time):
        Context._linux_set_time(float(time))
        try:
            subprocess.call(['/etc/init.d/ntp', 'restart'])
        except:
            pass

    @staticmethod
    def set_hostname(hostname):
        if distro == 'debian':
            with open('/etc/hostname', 'w') as f:
                f.write(hostname)
        elif distro == 'rhel':
            for line in fileinput.input('/etc/sysconfig/network',
                                        inplace=1):
                if line.startswith('HOSTNAME='):
                    print 'HOSTNAME=%s' % hostname
                else:
                    print line.rstrip()

        with open('/etc/hosts', 'w') as f:
            f.write("127.0.0.1 localhost\n"
                    "127.0.1.1 %s\n" % hostname)

        subprocess.call(['/bin/hostname', hostname])

    @staticmethod
    def mount_store(host, username, password):
        data = {'host': host, 'username': username, 'password': password}
        data['dir'] = STORE_DIR
        if not exists(STORE_DIR):
            mkdir(STORE_DIR)
        # TODO
        for line in fileinput.input('/etc/fstab', inplace=True):
            if not (line.startswith('//') and ' cifs ' in line):
                print line.rstrip()

        with open('/etc/fstab', 'a') as f:
            f.write(mount_template_linux % data)

        subprocess.call('mount -a', shell=True)

    @staticmethod
    def get_keys():
        retval = []
        try:
            with open(AUTHORIZED_KEYS, 'r') as f:
                for line in f.readlines():
                    try:
                        retval.append(PubKey.from_str(line))
                    except:
                        logger.exception(u'Invalid ssh key: ')
        except IOError:
            pass
        return retval

    @staticmethod
    def _save_keys(keys):
        print keys
        try:
            mkdir(SSH_DIR)
        except OSError:
            pass
        with open(AUTHORIZED_KEYS, 'w') as f:
            for key in keys:
                f.write(unicode(key) + '\n')

    @staticmethod
    def add_keys(keys):
        new_keys = Context.get_keys()
        for key in keys:
            try:
                p = PubKey.from_str(key)
                if p not in new_keys:
                    new_keys.append(p)
            except:
                logger.exception(u'Invalid ssh key: ')
        Context._save_keys(new_keys)

    @staticmethod
    def del_keys(keys):
        new_keys = Context.get_keys()
        for key in keys:
            try:
                p = PubKey.from_str(key)
                try:
                    new_keys.remove(p)
                except ValueError:
                    pass
            except:
                logger.exception(u'Invalid ssh key: ')
        Context._save_keys(new_keys)

    @staticmethod
    def cleanup():
        filelist = ([
            '/root/.bash_history'
            '/home/cloud/.bash_history'
            '/root/.ssh'
            '/home/cloud/.ssh']
            + glob('/etc/ssh/ssh_host_*'))
        for f in filelist:
            rmtree(f, ignore_errors=True)

        subprocess.call(('/usr/bin/ssh-keygen', '-A'))

    @staticmethod
    def start_access_server():
        try:
            subprocess.call(('/sbin/start', 'ssh'))
        except OSError:
            subprocess.call(('/bin/systemctl', 'start', 'sshd.service'))

    @staticmethod
    def append(data, filename, chunk_number, uuid):
        if chunk_number == 0:
            flag = "w"
        else:
            flag = "a"
        with open(filename, flag) as myfile:
            myfile.write(data)

    @staticmethod
    def update(filename, executable, checksum, uuid):
        new_dir = working_directory + '.new'
        old_dir = working_directory + '.old.%s' % uuid
        with open(filename, "r") as f:
            data = f.read()
            local_checksum = md5(data).hexdigest()
            if local_checksum != checksum:
                raise Exception("Checksum missmatch the file is damaged.")
            decoded = StringIO(decodestring(data))
        try:
            tar = tarfile.TarFile.open("dummy", fileobj=decoded, mode='r|gz')
            tar.extractall(new_dir)
        except tarfile.ReadError as e:
            logger.error(e)
        move(working_directory, old_dir)
        move(new_dir, working_directory)
        logger.info("Transfer completed!")
        reactor.stop()

    @staticmethod
    def ipaddresses():
        import netifaces
        args = {}
        interfaces = netifaces.interfaces()
        for i in interfaces:
            if i == 'lo':
                continue
            args[i] = []
            addresses = netifaces.ifaddresses(i)
            args[i] = ([x['addr']
                        for x in addresses.get(netifaces.AF_INET, [])] +
                       [x['addr']
                        for x in addresses.get(netifaces.AF_INET6, [])
                        if '%' not in x['addr']])
        return args

    @staticmethod
    def get_agent_version():
        try:
            with open('version.txt') as f:
                return f.readline()
        except IOError:
            return None

    @staticmethod
    def send_expiration(url):
        import notify
        notify.notify(url)