# -*- coding: utf-8 -*-
# vi:si:et:sw=4:sts=4:ts=4


from contextlib import closing
import json
import os
import tarfile
import urllib.request, urllib.error, urllib.parse
import shutil
import subprocess

import ed25519
import ox
from oxtornado import actions

import settings


ENCODING='base64'

def verify(release):
    vk = ed25519.VerifyingKey(settings.OML_UPDATE_KEY, encoding=ENCODING)
    value = []
    for module in sorted(release['modules']):
        value += [str('%s/%s' % (release['modules'][module]['version'], release['modules'][module]['sha1']))]
    value = '\n'.join(value)
    value = value.encode()
    sig = release['signature'].encode()
    try:
        vk.verify(sig, value, encoding=ENCODING)
    except ed25519.BadSignatureError:
        return False
    return True

def get(url, filename=None):
    request = urllib.request.Request(url, headers={
        'User-Agent': settings.USER_AGENT
    })
    with closing(urllib.request.urlopen(request)) as u:
        if not filename:
            data = u.read()
            return data
        else:
            dirname = os.path.dirname(filename)
            if dirname and not os.path.exists(dirname):
                os.makedirs(dirname)
            with open(filename, 'wb') as fd:
                data = u.read(4096)
                while data:
                    fd.write(data)
                    data = u.read(4096)

def check():
    if settings.release:
        release_data = get(settings.server.get('release_url'))
        release = json.loads(release_data.decode('utf-8'))
        old = settings.release['modules']['openmedialibrary']['version']
        new = release['modules']['openmedialibrary']['version']
        return verify(release) and old < new
    return False

def download():
    if not os.path.exists(os.path.join(settings.config_path, 'release.json')):
        return True
    release_data = get(settings.server.get('release_url'))
    release = json.loads(release_data.decode('utf-8'))
    if verify(release):
        ox.makedirs(settings.updates_path)
        os.chdir(os.path.dirname(settings.base_dir))
        current_files = {'release.json'}
        for module in release['modules']:
            if release['modules'][module]['version'] > settings.release['modules'][module]['version']:
                module_tar = os.path.join(settings.updates_path, release['modules'][module]['name'])
                base_url = settings.server.get('release_url').rsplit('/', 1)[0]
                url = '/'.join([base_url, release['modules'][module]['name']])
                if not os.path.exists(module_tar):
                    print('download', os.path.basename(module_tar))
                    get(url, module_tar)
                    if ox.sha1sum(module_tar) != release['modules'][module]['sha1']:
                        os.unlink(module_tar)
                        return False
                current_files.add(os.path.basename(module_tar))
        with open(os.path.join(settings.updates_path, 'release.json'), 'wb') as fd:
            fd.write(release_data)
        for f in set(next(os.walk(settings.updates_path))[2])-current_files:
            os.unlink(os.path.join(settings.updates_path, f))
        return True
    return True

def install():
    if not os.path.exists(os.path.join(settings.updates_path, 'release.json')):
        return True
    if not os.path.exists(os.path.join(settings.config_path, 'release.json')):
        return True
    with open(os.path.join(settings.updates_path, 'release.json')) as fd:
        release = json.load(fd)
    old = settings.release['modules']['openmedialibrary']['version']
    new = release['modules']['openmedialibrary']['version']
    if verify(release) and old < new:
        os.chdir(os.path.dirname(settings.base_dir))
        for module in release['modules']:
            if release['modules'][module]['version'] > settings.release['modules'][module]['version']:
                module_tar = os.path.join(settings.updates_path, release['modules'][module]['name'])
                if os.path.exists(module_tar) and ox.sha1sum(module_tar) == release['modules'][module]['sha1']:
                    #tar fails if old platform is moved before extract
                    new = '%s_new' % module
                    ox.makedirs(new)
                    os.chdir(new)
                    tar = tarfile.open(module_tar)
                    tar.extractall()
                    tar.close()
                    os.chdir(os.path.dirname(settings.base_dir))
                    shutil.move(module, '%s_old' % module)
                    shutil.move(os.path.join(new, module), module)
                    shutil.rmtree('%s_old' % module)
                    shutil.rmtree(new)
                else:
                    os.unlink(module_tar)
                    return False
        shutil.copy(os.path.join(settings.updates_path, 'release.json'), os.path.join(settings.config_path, 'release.json'))
        for cmd in [
                ['./ctl', 'stop'],
                ['./ctl', 'setup'],
                ['./ctl', 'postupdate', '-o', old, '-n', new]
            ]:
            subprocess.call(cmd)
        return True
    return True

def getVersion(data):
    '''
        check if new version is available
    '''
    response = {
        'current': settings.MINOR_VERSION,
        'upgrade': False,
    }
    if settings.MINOR_VERSION == 'git':
        cmd = ['git', 'rev-parse', '@']
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True)
        stdout, stderr = p.communicate()
        current = stdout.strip()
        cmd = ['git', 'ls-remote', 'origin', '-h', 'refs/heads/master']
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, close_fds=True)
        stdout, stderr = p.communicate()
        new = stdout.strip()[:40]
        response['update'] = current != new
    else:
        if not os.path.exists(os.path.join(settings.updates_path, 'release.json')):
            return response
        if not os.path.exists(os.path.join(settings.config_path, 'release.json')):
            return response
        with open(os.path.join(settings.updates_path, 'release.json')) as fd:
            release = json.load(fd)
        current = settings.release['modules']['openmedialibrary']['version']
        response['current'] = current
        new = release['modules']['openmedialibrary']['version']
        response['new'] = new
        response['update'] = current < new
    return response
actions.register(getVersion, cache=False)

def restart(data):
    '''
        restart (and upgrade if upgrades are available)
    '''
    subprocess.Popen([os.path.join(settings.base_dir, 'ctl'), 'restart'], close_fds=True)
    return {}
actions.register(restart, cache=False)