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

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

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

try:  # noqa
    # load virtio console driver, the device is /dev/ttyV0.1  # noqa
    subprocess.call(('/sbin/kldload', '-n', 'virtio_console'))  # noqa
    subprocess.call(('/sbin/kldload', '-n', 'smbfs'))  # noqa
    chdir(working_directory)  # noqa
    subprocess.call(  # noqa
        ('/usr/local/bin/pip', 'install', '-r', 'requirements.txt'))  # noqa
    copy("/root/agent/misc/vm_renewal", "/usr/local/bin/")  # noqa
except Exception:  # noqa
    pass  # hope it works  # noqa


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_freebsd
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_freebsd = (
    '//CLOUD/%(username)s %(dir)s smbfs '
    'rw,-N,-ucloud,-U%(username)s  0  0\n')

NSMBRC = '/etc/nsmb.conf'
nsmbrc_template_freebsd = (
    '[CLOUD:%(USERNAME)s]\n'
    'addr=%(host)s\n'
    'password=%(password)s\n'
    '\n'
    '[CLOUD]\n'
    'addr=%(host)s\n'
    'password=%(password)s\n')


class Context(BaseContext):

    # http://stackoverflow.com/questions/12081310/
    # python-module-to-change-system-date-and-time
    @staticmethod
    def _freebsd_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):
        proc0 = subprocess.Popen(
            ['/usr/sbin/pw', 'user', 'mod', 'cloud', '-h', '0'],
            stdin=subprocess.PIPE)
        proc0.communicate('%s\n' % password)
        proc1 = subprocess.Popen(
            ['/usr/sbin/pw', 'user', 'mod', 'root', '-h', '0'],
            stdin=subprocess.PIPE)
        proc1.communicate('%s\n' % password)

    @staticmethod
    def restart_networking():
        logger.debug("restart_networking")

    @staticmethod
    def change_ip(interfaces, dns):
        nameservers = dns.replace(' ', '').split(',')
        change_ip_freebsd(interfaces, nameservers)

    @staticmethod
    def set_time(time):
        Context._freebsd_set_time(float(time))
        try:
            subprocess.call(['/usr/sbin/service' 'ntpd', 'onerestart'])
        except Exception:
            pass

    @staticmethod
    def set_hostname(hostname):
        with open('/etc/rc.conf.d/hostname', 'w') as f:
            f.write("hostname=\""+hostname+"\"")

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

        subprocess.call(['/usr/sbin/service', 'hostname', 'restart'])

    @staticmethod
    def mount_store(host, username, password):
        data = {'host': host, 'username': username, 'password': password,
                'USERNAME': username.upper()}
        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 ' smbfs ' in line):
                print line.rstrip()

        with open(NSMBRC, 'w') as f:
            chmod(NSMBRC, 0600)
            f.write(nsmbrc_template_freebsd % data)

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

        subprocess.call('/sbin/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 Exception:
                        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 Exception:
                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 Exception:
                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',
            '/root/.lesshst',
            '/home/cloud/.lesshst',
            '/root/.history',
            '/home/cloud/.history',
            '/root/.viminfo',
            '/home/cloud/.viminfo',
            '/etc/nsmb.conf',
            '/root/.nsmbrc.conf',
            '/home/cloud/.nsmbrc.conf',
            ]
            + 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():
        subprocess.call(('/usr/sbin/service', 'ssh', 'start'))

    @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 == 'lo0':
                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)
