##
# Copyright 2016
# This file is part of openvim
#
# 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.
#
##

import os
import json
import time
import subprocess
from git import Repo as gitrepo
from shutil import rmtree

from charms.reactive import when, when_not, set_state
from charmhelpers.core.templating import render
from charmhelpers.core.hookenv import (
    status_set,
    leader_set,
    leader_get,
    unit_public_ip,
)
from charmhelpers.core.unitdata import kv
from charmhelpers.core.host import (
    symlink,
    mkdir,
    chownr,
    service_start,
)
from charmhelpers.contrib.unison import (
    create_private_key,
    create_public_key,
    ensure_user,
)


def sh(cmd):
    return subprocess.check_output(cmd, shell=True)


def sh_as_openvim(cmd):
    return sh('sudo -iu openvim ' + cmd)


def create_openvim_user():
    status_set("maintenance", "Creating OpenVIM user")
    ensure_user('openvim')


def initialize_openvim_database(db):
    status_set("maintenance", "Initializing OpenVIM database")
    sh_as_openvim("/opt/openmano/openvim/database_utils/init_vim_db.sh -u %s -p %s -d %s -h %s" % (
        db.user(),
        db.password(),
        db.database(),
        db.host()
    ))


def generate_ssh_key():
    status_set("maintenance", "Generating ssh key")
    user = "openvim"
    folder = "/home/%s/.ssh" % user
    mkdir(folder, owner=user, group=user, perms=0o775)
    private_path = "%s/id_rsa" % folder
    public_path = "%s.pub" % private_path
    create_private_key(user, private_path)
    create_public_key(user, private_path, public_path)


def add_openvim_to_path():
    status_set("maintenance", "Adding OpenVIM to path")
    symlink(
        '/opt/openmano/scripts/service-openmano.sh',
        '/usr/bin/service-openmano')
    symlink('/opt/openmano/openvim/openvim', '/usr/bin/openvim')


def download_openvim():
    status_set("maintenance", "Downloading OpenVIM")
    if os.path.isdir("/opt/openmano"):
        rmtree("/opt/openmano")
    gitrepo.clone_from('https://github.com/tvansteenburgh/openmano.git', '/opt/openmano')
    chownr('/opt/openmano', owner='openvim', group='openvim', follow_links=False, chowntopdir=True)


def configure_openvim(db):
    status_set("maintenance", "Configuring OpenVIM")
    render(
        source="openvimd.cfg",
        target="/opt/openmano/openvim/openvimd.cfg",
        owner="openvim",
        perms=0o664,
        context={"db": db}
    )


# TODO: possibly combine all of these create functions?
def create_tenant():
    status_set("maintenance", "Creating tenant")
    render(source="tenant.yaml", target="/tmp/tenant.yaml", owner="openvim", perms=0o664, context={})
    cmd = 'openvim tenant-create /tmp/tenant.yaml'
    tenant_uuid = sh_as_openvim(cmd).split()[0]
    tenant_uuid = str(tenant_uuid, 'utf-8')
    leader_set({'tenant': tenant_uuid})
    return tenant_uuid


def create_image():
    status_set("maintenance", "Creating image")
    render(source="image.yaml", target="/tmp/image.yaml", owner="openvim", perms=0o664, context={})
    cmd = 'openvim image-create /tmp/image.yaml'
    image_uuid = sh_as_openvim(cmd).split()[0]
    image_uuid = str(image_uuid, 'utf-8')
    return image_uuid


def create_flavor():
    status_set("maintenance", "Creating flavor")
    render(source="flavor.yaml", target="/tmp/flavor.yaml", owner="openvim", perms=0o664, context={})
    cmd = 'openvim flavor-create /tmp/flavor.yaml'
    flavor_uuid = sh_as_openvim(cmd).split()[0]
    flavor_uuid = str(flavor_uuid, 'utf-8')
    return flavor_uuid


# TODO: especially combine these stupid network functions
def create_default_network():
    status_set("maintenance", "Creating default network")
    render(source="net-default.yaml", target="/tmp/net-default.yaml", owner="openvim", perms=0o664, context={})
    cmd = 'openvim net-create /tmp/net-default.yaml'
    net_default_uuid = sh_as_openvim(cmd).split()[0]
    net_default_uuid = str(net_default_uuid, 'utf-8')
    return net_default_uuid


def create_virbr_network():
    status_set("maintenance", "Creating virbr0 network")
    render(source="net-virbr0.yaml", target="/tmp/net-virbr0.yaml", owner="openvim", perms=0o664, context={})
    cmd = 'openvim net-create /tmp/net-virbr0.yaml'
    net_virbr0_uuid = sh_as_openvim(cmd).split()[0]
    net_virbr0_uuid = str(net_virbr0_uuid, 'utf-8')
    return net_virbr0_uuid


def create_vm_yaml(image_uuid, flavor_uuid, net_default_uuid, net_virbr0_uuid):
    status_set("maintenance", "Creating default VM yaml file")
    render(
        source="server.yaml",
        target="/tmp/server.yaml",
        owner="openvim",
        perms=0o664,
        context={
            "image_uuid": image_uuid,
            "flavor_uuid": flavor_uuid,
            "net_default_uuid": net_default_uuid,
            "net_virbr0_uuid": net_virbr0_uuid
        }
    )


def create_sane_defaults():
    tenant_uuid = create_tenant()
    add_openvim_tenant_env_var(tenant_uuid)
    image_uuid = create_image()
    flavor_uuid = create_flavor()
    net_default_uuid = create_default_network()
    net_virbr0_uuid = create_virbr_network()
    create_vm_yaml(
        image_uuid=image_uuid,
        flavor_uuid=flavor_uuid,
        net_default_uuid=net_default_uuid,
        net_virbr0_uuid=net_virbr0_uuid
    )


def install_openvim_service():
    status_set("maintenance", "Installing OpenVIM service")
    if not os.path.exists('/etc/systemd/system'):
        os.makedirs('/etc/systemd/system')
    render(
        source="openvim.service",
        target="/etc/systemd/system/openvim.service",
        owner="root",
        perms=0o644,
        context={}
    )


def add_openvim_tenant_env_var(tenant_uuid):
    status_set("maintenance", "Adding OPENVIM_TENANT environment variable")
    env_line = 'export OPENVIM_TENANT=%s\n' % tenant_uuid
    with open('/home/openvim/.profile', 'w+') as f:
        lines = f.readlines()
        for line in lines:
            if env_line == line:
                return
        f.seek(0)
        f.truncate()
        for line in lines:
            f.write(line)
        f.write(env_line)


def openvim_running():
    try:
        sh_as_openvim('openvim tenant-list')
        return True
    except:
        return False


def start_openvim():
    status_set("maintenance", "Starting OpenVIM")
    service_start('openvim')
    t0 = time.time()
    while not openvim_running():
        if time.time() - t0 > 60:
            raise Exception('Failed to start openvim.')
        time.sleep(0.25)


@when_not('db.available')
def not_ready():
    status_set('waiting', 'MySQL database required')


@when('db.available')
@when_not('openvim-controller.installed')
def install_openvim_controller(mysql):
    create_openvim_user()
    download_openvim()
    add_openvim_to_path()
    configure_openvim(mysql)
    initialize_openvim_database(mysql)
    generate_ssh_key()
    install_openvim_service()
    start_openvim()
    create_sane_defaults()
    status_set(
        'active',
        'Up on {host}:{port}'.format(
            host=unit_public_ip(),
            port='9080'))
    set_state('openvim-controller.installed')


@when('compute.connected', 'openvim-controller.installed')
def send_ssh_key(compute):
    with open('/home/openvim/.ssh/id_rsa.pub', 'r') as f:
        key = f.read().strip()
    compute.send_ssh_key(key)


@when('compute.available', 'openvim-controller.installed')
def host_add(compute):
    cache = kv()
    for node in compute.authorized_nodes():
        if cache.get("compute:" + node['address']):
            continue
        cmd = "ssh -n -o 'StrictHostKeyChecking no' %s@%s"
        sh_as_openvim(cmd % (node['user'], node['address']))
        data = {
            'host': {
                'name': 'compute-0',
                'user': node['user'],
                'ip_name': node['address'],
                'description': 'compute-0'
            }
        }
        with open('/tmp/compute-0.json', 'w') as f:
            json.dump(data, f, indent=4, sort_keys=True)
        # TODO: openvim run function!
        sh_as_openvim('openvim host-add /tmp/compute-0.json')
        cache.set('compute:' + node['address'], True)


@when('openvim-controller.available')
def openvim_available(openvim):
    openvim.configure(port=9080, user=leader_get('tenant'))
